@openzeppelin/ui-builder-adapter-stellar
Version:
Stellar Adapter for UI Builder
1 lines • 395 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/contract/loader.ts","../src/validation/address.ts","../src/validation/eoa.ts","../src/validation/relayer.ts","../src/configuration/explorer.ts","../src/mapping/struct-fields.ts","../src/utils/type-detection.ts","../src/query/handler.ts","../src/transform/parsers/index.ts","../src/transform/parsers/generic-parser.ts","../src/transform/parsers/primitive-parser.ts","../src/transform/parsers/complex-parser.ts","../src/utils/safe-type-parser.ts","../src/utils/formatting.ts","../src/utils/input-parsing.ts","../src/transform/parsers/scval-converter.ts","../src/transform/parsers/struct-parser.ts","../src/transform/output-formatter.ts","../src/types/artifacts.ts","../src/utils/artifacts.ts","../src/query/view-checker.ts","../src/sac/spec-cache.ts","../src/sac/spec-source.ts","../src/sac/xdr.ts","../src/contract/type.ts","../src/transaction/components/StellarRelayerOptions.tsx","../src/transaction/components/AdvancedInfo.tsx","../src/transaction/components/FeeConfiguration.tsx","../src/transaction/components/TransactionTiming.tsx","../src/transaction/components/useStellarRelayerOptions.ts","../src/transaction/relayer.ts","../src/wallet/connection.ts","../src/wallet/utils/stellarWalletImplementationManager.ts","../src/wallet/implementation/wallets-kit-implementation.ts","../src/wallet/stellar-wallets-kit/stellarUiKitManager.ts","../src/wallet/stellar-wallets-kit/config-generator.ts","../src/wallet/stellar-wallets-kit/export-service.ts","../src/wallet/stellar-wallets-kit/StellarWalletsKitConnectButton.tsx","../src/configuration/execution.ts","../src/configuration/rpc.ts","../src/mapping/constants.ts","../src/mapping/type-mapper.ts","../src/mapping/field-generator.ts","../src/mapping/enum-metadata.ts","../src/transaction/formatter.ts","../src/transaction/sender.ts","../src/transaction/eoa.ts","../src/wallet/components/StellarWalletUiRoot.tsx","../src/wallet/context/StellarWalletContext.ts","../src/wallet/context/useStellarWalletContext.ts","../src/wallet/hooks/useStellarAccount.ts","../src/wallet/hooks/useStellarConnect.ts","../src/wallet/hooks/useStellarDisconnect.ts","../src/wallet/hooks/facade-hooks.ts","../src/wallet/hooks/useUiKitConfig.ts","../src/wallet/components/connect/ConnectButton.tsx","../src/wallet/components/connect/ConnectorDialog.tsx","../src/wallet/components/account/AccountDisplay.tsx","../src/wallet/utils/filterWalletComponents.ts","../src/wallet/utils/uiKitService.ts","../src/wallet/services/configResolutionService.ts","../src/networks/mainnet.ts","../src/networks/testnet.ts","../src/networks/index.ts","../src/config.ts"],"sourcesContent":["// Re-export the main adapter class\nexport { StellarAdapter } from './adapter';\n\n// Re-export adapter-specific types\nexport type { StellarContractArtifacts } from './types/artifacts';\nexport { isStellarContractArtifacts } from './types/artifacts';\n\nexport {\n stellarNetworks,\n stellarMainnetNetworks,\n stellarTestnetNetworks,\n // Individual networks\n stellarPublic,\n stellarTestnet,\n} from './networks';\n\n// Export adapter configuration\nexport { stellarAdapterConfig } from './config';\n","import type 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 RelayerDetails,\n RelayerDetailsRich,\n StellarNetworkConfig,\n TransactionStatusUpdate,\n TxStatus,\n UiKitConfiguration,\n UserRpcProviderConfig,\n WalletConnectionStatus,\n} from '@openzeppelin/ui-builder-types';\nimport { isStellarNetworkConfig } from '@openzeppelin/ui-builder-types';\nimport { logger } from '@openzeppelin/ui-builder-utils';\n\n// Import functions from modules\nimport { loadStellarContract, loadStellarContractWithMetadata } from './contract/loader';\nimport { StellarRelayerOptions } from './transaction/components';\nimport { RelayerExecutionStrategy } from './transaction/relayer';\n\nimport {\n getStellarExplorerAddressUrl,\n getStellarExplorerTxUrl,\n getStellarSupportedExecutionMethods,\n testStellarRpcConnection,\n validateStellarExecutionConfig,\n validateStellarRpcEndpoint,\n} from './configuration';\nimport {\n generateStellarDefaultField,\n getStellarCompatibleFieldTypes,\n mapStellarParameterTypeToFieldType,\n} from './mapping';\nimport {\n getStellarWritableFunctions,\n isStellarViewFunction,\n queryStellarViewFunction,\n} from './query';\nimport { formatStellarTransactionData, signAndBroadcastStellarTransaction } from './transaction';\nimport { formatStellarFunctionResult } from './transform';\nimport { validateAndConvertStellarArtifacts } from './utils';\nimport { isValidAddress as isStellarValidAddress, type StellarAddressType } from './validation';\nimport {\n connectStellarWallet,\n disconnectStellarWallet,\n generateStellarWalletsKitExportables,\n getInitializedStellarWalletImplementation,\n getResolvedWalletComponents,\n getStellarAvailableConnectors,\n getStellarWalletImplementation,\n loadInitialConfigFromAppService,\n resolveFullUiKitConfiguration,\n stellarFacadeHooks,\n stellarUiKitManager,\n StellarWalletConnectionStatus,\n StellarWalletUiRoot,\n supportsStellarWalletConnection,\n} from './wallet';\n\n/**\n * Stellar-specific adapter implementation using explicit method delegation.\n *\n * NOTE: Contains placeholder implementations for most functionalities.\n */\nexport class StellarAdapter implements ContractAdapter {\n readonly networkConfig: StellarNetworkConfig;\n readonly initialAppServiceKitName: UiKitConfiguration['kitName'];\n\n constructor(networkConfig: StellarNetworkConfig) {\n if (!isStellarNetworkConfig(networkConfig)) {\n throw new Error('StellarAdapter requires a valid Stellar network configuration.');\n }\n this.networkConfig = networkConfig;\n\n // Set the network config in the wallet UI kit manager\n stellarUiKitManager.setNetworkConfig(networkConfig);\n\n // Initialize wallet implementation with network config\n getStellarWalletImplementation(networkConfig).catch((error) => {\n logger.error(\n 'StellarAdapter:constructor',\n 'Failed to initialize wallet implementation:',\n error\n );\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 const initialGlobalConfig = loadInitialConfigFromAppService();\n this.initialAppServiceKitName =\n (initialGlobalConfig.kitName as UiKitConfiguration['kitName']) || 'custom';\n\n logger.info(\n 'StellarAdapter:constructor',\n 'Initial kitName from AppConfigService noted:',\n this.initialAppServiceKitName\n );\n\n logger.info(\n 'StellarAdapter',\n `Adapter initialized for network: ${networkConfig.name} (ID: ${networkConfig.id})`\n );\n }\n\n // --- Contract Loading --- //\n /**\n * NOTE about artifact inputs (single input with auto-detection):\n *\n * The Builder renders the contract definition step using whatever fields the\n * adapter returns here. EVM uses one optional ABI field; Midnight provides\n * multiple fields. Stellar should use a single input approach with automatic\n * content detection when we add manual-spec support:\n *\n * - Keep `contractAddress` (required)\n * - Add optional `contractDefinition` (type: `code-editor`, language: `json`)\n * with file upload support for both JSON and Wasm binary content\n *\n * When this field is added:\n * - Extend `validateAndConvertStellarArtifacts(...)` to accept\n * `{ contractAddress, contractDefinition? }`\n * - In the loader, branch: if `contractDefinition` provided, auto-detect\n * content type (JSON vs Wasm using magic bytes `\\0asm`):\n * - For JSON: Parse and validate as Soroban spec, use `transformStellarSpecToSchema`\n * - For Wasm: Extract embedded spec from binary, parse locally (no RPC)\n * - Set `source: 'manual'` with `contractDefinitionOriginal` to the raw\n * user-provided content. This ensures auto-save captures and restores the\n * manual contract definition exactly like the EVM/Midnight flows.\n * - Provide clear UI hints about supported formats (JSON spec or Wasm binary).\n */\n public getContractDefinitionInputs(): FormFieldType[] {\n return [\n {\n id: 'contractAddress',\n name: 'contractAddress',\n label: 'Contract ID',\n type: 'blockchain-address',\n validation: { required: true },\n placeholder: 'C...',\n helperText: 'Enter the Stellar contract ID (C...).',\n },\n ];\n }\n\n /**\n * @inheritdoc\n */\n public async loadContract(source: string | Record<string, unknown>): Promise<ContractSchema> {\n // Convert generic input to Stellar-specific artifacts\n const artifacts = validateAndConvertStellarArtifacts(source);\n const result = await loadStellarContract(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 fetchTimestamp?: Date;\n definitionHash?: string;\n };\n }> {\n try {\n // Convert generic input to Stellar-specific artifacts\n const artifacts = validateAndConvertStellarArtifacts(source);\n const result = await loadStellarContractWithMetadata(artifacts, this.networkConfig);\n\n return {\n schema: result.schema,\n source: result.source,\n contractDefinitionOriginal: result.contractDefinitionOriginal,\n metadata: result.metadata,\n };\n } catch (error) {\n // Re-throw errors consistently with EVM adapter\n throw error;\n }\n }\n\n getWritableFunctions(contractSchema: ContractSchema): ContractSchema['functions'] {\n return getStellarWritableFunctions(contractSchema);\n }\n\n // --- Type Mapping & Field Generation --- //\n mapParameterTypeToFieldType(parameterType: string): FieldType {\n return mapStellarParameterTypeToFieldType(parameterType);\n }\n getCompatibleFieldTypes(parameterType: string): FieldType[] {\n return getStellarCompatibleFieldTypes(parameterType);\n }\n generateDefaultField<T extends FieldType = FieldType>(\n parameter: FunctionParameter,\n contractSchema?: ContractSchema\n ): FormFieldType<T> {\n return generateStellarDefaultField(parameter, contractSchema);\n }\n\n // --- Transaction Formatting & Execution --- //\n public formatTransactionData(\n contractSchema: ContractSchema,\n functionId: string,\n submittedInputs: Record<string, unknown>,\n fields: FormFieldType[]\n ): unknown {\n return formatStellarTransactionData(contractSchema, functionId, submittedInputs, fields);\n }\n async signAndBroadcast(\n transactionData: unknown,\n executionConfig: ExecutionConfig,\n onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,\n runtimeApiKey?: string\n ): Promise<{ txHash: string }> {\n return signAndBroadcastStellarTransaction(\n transactionData,\n executionConfig,\n this.networkConfig,\n onStatusChange,\n runtimeApiKey\n );\n }\n\n // NOTE: waitForTransactionConfirmation? is optional in the interface.\n // Since the imported function is currently undefined, we omit the method here.\n // If implemented in ./transaction/sender.ts later, add the method back:\n // async waitForTransactionConfirmation?(...) { ... }\n\n // --- View Function Querying --- //\n isViewFunction(functionDetails: ContractFunction): boolean {\n return isStellarViewFunction(functionDetails);\n }\n\n // Implement queryViewFunction with the correct signature from ContractAdapter\n async queryViewFunction(\n contractAddress: string,\n functionId: string,\n params: unknown[] = [],\n contractSchema?: ContractSchema\n ): Promise<unknown> {\n return queryStellarViewFunction(\n contractAddress,\n functionId,\n this.networkConfig,\n params,\n contractSchema,\n (address: string) => this.loadContract({ contractAddress: address })\n );\n }\n\n formatFunctionResult(decodedValue: unknown, functionDetails: ContractFunction): string {\n return formatStellarFunctionResult(decodedValue, functionDetails);\n }\n\n // --- Wallet Interaction --- //\n supportsWalletConnection(): boolean {\n return supportsStellarWalletConnection();\n }\n async getAvailableConnectors(): Promise<Connector[]> {\n return getStellarAvailableConnectors();\n }\n async connectWallet(\n connectorId: string\n ): Promise<{ connected: boolean; address?: string; error?: string }> {\n return connectStellarWallet(connectorId);\n }\n async disconnectWallet(): Promise<{ disconnected: boolean; error?: string }> {\n return disconnectStellarWallet();\n }\n getWalletConnectionStatus(): StellarWalletConnectionStatus {\n const impl = getInitializedStellarWalletImplementation();\n if (!impl) {\n return {\n isConnected: false,\n address: undefined,\n chainId: stellarUiKitManager.getState().networkConfig?.id || 'stellar-testnet',\n };\n }\n\n const stellarStatus = impl.getWalletConnectionStatus();\n\n // Return the rich Stellar-specific status directly\n return stellarStatus;\n }\n\n /**\n * @inheritdoc\n */\n onWalletConnectionChange(\n callback: (\n currentStatus: WalletConnectionStatus,\n previousStatus: WalletConnectionStatus\n ) => void\n ): () => void {\n const walletImplementation = getInitializedStellarWalletImplementation();\n if (!walletImplementation) {\n logger.warn(\n 'StellarAdapter:onWalletConnectionChange',\n 'Wallet implementation not ready. Subscription may not work.'\n );\n return () => {};\n }\n\n return walletImplementation.onWalletConnectionChange(\n (currentImplStatus, previousImplStatus) => {\n callback(currentImplStatus, previousImplStatus);\n }\n );\n }\n\n // --- Configuration & Metadata --- //\n async getSupportedExecutionMethods(): Promise<ExecutionMethodDetail[]> {\n return getStellarSupportedExecutionMethods();\n }\n async validateExecutionConfig(config: ExecutionConfig): Promise<true | string> {\n const walletStatus = this.getWalletConnectionStatus();\n return validateStellarExecutionConfig(config, walletStatus);\n }\n\n // Implement getExplorerUrl with the correct signature from ContractAdapter\n getExplorerUrl(address: string): string | null {\n return getStellarExplorerAddressUrl(address, this.networkConfig);\n }\n\n // Implement getExplorerTxUrl with the correct signature from ContractAdapter\n getExplorerTxUrl?(txHash: string): string | null {\n if (getStellarExplorerTxUrl) {\n return getStellarExplorerTxUrl(txHash, this.networkConfig);\n }\n return null;\n }\n\n // --- Validation --- //\n isValidAddress(address: string, addressType?: string): boolean {\n return isStellarValidAddress(address, addressType as StellarAddressType);\n }\n\n public async getAvailableUiKits(): Promise<AvailableUiKit[]> {\n return [\n {\n id: 'custom',\n name: 'Stellar Wallets Kit Custom',\n configFields: [],\n },\n {\n id: 'stellar-wallets-kit',\n name: 'Stellar Wallets Kit',\n configFields: [],\n },\n ];\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 // Configure the Stellar UI kit manager\n await stellarUiKitManager.configure(finalFullConfig);\n logger.info(\n 'StellarAdapter:configureUiKit',\n 'StellarUiKitManager configuration requested with final config:',\n finalFullConfig\n );\n }\n\n /**\n * @inheritdoc\n */\n public async getExportableWalletConfigFiles(\n uiKitConfig?: UiKitConfiguration\n ): Promise<Record<string, string>> {\n if (uiKitConfig?.kitName === 'stellar-wallets-kit') {\n return generateStellarWalletsKitExportables(uiKitConfig);\n }\n return {};\n }\n\n /**\n * @inheritdoc\n */\n public getEcosystemWalletComponents(): EcosystemWalletComponents | undefined {\n const currentManagerState = stellarUiKitManager.getState();\n\n // Only attempt to resolve components if the manager has a configuration set\n if (!currentManagerState.currentFullUiKitConfig) {\n logger.debug(\n 'StellarAdapter:getEcosystemWalletComponents',\n 'No UI kit configuration available in manager yet. Returning undefined components.'\n );\n return undefined;\n }\n\n // Use the service to resolve components based on the current UI kit configuration\n const components = getResolvedWalletComponents(currentManagerState.currentFullUiKitConfig);\n return components;\n }\n\n /**\n * @inheritdoc\n */\n public getEcosystemReactUiContextProvider():\n | React.ComponentType<EcosystemReactUiProviderProps>\n | undefined {\n logger.info(\n 'StellarAdapter:getEcosystemReactUiContextProvider',\n 'Returning StellarWalletUiRoot.'\n );\n return StellarWalletUiRoot;\n }\n\n /**\n * @inheritdoc\n */\n public getEcosystemReactHooks(): EcosystemSpecificReactHooks | undefined {\n // Always provide hooks for Stellar adapter regardless of UI kit\n return stellarFacadeHooks;\n }\n\n public async getRelayers(serviceUrl: string, accessToken: string): Promise<RelayerDetails[]> {\n const relayerStrategy = new RelayerExecutionStrategy();\n try {\n return await relayerStrategy.getStellarRelayers(serviceUrl, accessToken, this.networkConfig);\n } catch (error) {\n logger.error('StellarAdapter', 'Failed to fetch Stellar relayers:', error);\n return Promise.resolve([]);\n }\n }\n\n public async getRelayer(\n serviceUrl: string,\n accessToken: string,\n relayerId: string\n ): Promise<RelayerDetailsRich> {\n const relayerStrategy = new RelayerExecutionStrategy();\n try {\n return await relayerStrategy.getStellarRelayer(\n serviceUrl,\n accessToken,\n relayerId,\n this.networkConfig\n );\n } catch (error) {\n logger.error('StellarAdapter', 'Failed to fetch Stellar relayer details:', error);\n return Promise.resolve({} as RelayerDetailsRich);\n }\n }\n\n /**\n * Returns a React component for configuring Stellar-specific relayer transaction options.\n * @returns The Stellar 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 StellarRelayerOptions;\n }\n\n /**\n * @inheritdoc\n */\n public async validateRpcEndpoint(rpcConfig: UserRpcProviderConfig): Promise<boolean> {\n // TODO: Implement Stellar-specific RPC validation when needed\n return validateStellarRpcEndpoint(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 // TODO: Implement Stellar-specific RPC validation when needed\n return testStellarRpcConnection(rpcConfig);\n }\n\n /**\n * @inheritdoc\n */\n public getUiLabels(): Record<string, string> | undefined {\n return {\n relayerConfigTitle: 'Transaction Configuration',\n relayerConfigActiveDesc: 'Customize transaction parameters for submission',\n relayerConfigInactiveDesc: 'Using recommended transaction configuration for reliability',\n relayerConfigPresetTitle: 'Recommended Preset Active',\n relayerConfigPresetDesc: 'Transactions will use recommended parameters for quick inclusion',\n relayerConfigCustomizeBtn: 'Customize 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 // For Stellar, sequence number is conceptually similar; adapters supply the value\n nonce: 'Sequence',\n pending: 'Pending Transactions',\n lastTransaction: 'Last Transaction',\n };\n }\n}\n\n// Also export as default to ensure compatibility with various import styles\nexport default StellarAdapter;\n","import * as StellarSdk from '@stellar/stellar-sdk';\nimport { xdr } from '@stellar/stellar-sdk';\n\nimport type {\n ContractFunction,\n ContractSchema,\n FunctionParameter,\n StellarNetworkConfig,\n} from '@openzeppelin/ui-builder-types';\nimport { logger } from '@openzeppelin/ui-builder-utils';\n\nimport { getStellarExplorerAddressUrl } from '../configuration/explorer';\nimport { extractStructFields, isStructType } from '../mapping/struct-fields';\nimport { checkStellarFunctionStateMutability } from '../query/handler';\nimport { getSacSpecArtifacts } from '../sac/spec-cache';\nimport type { StellarContractArtifacts } from '../types/artifacts';\nimport { extractSorobanTypeFromScSpec } from '../utils/type-detection';\nimport { getStellarContractType } from './type';\n\n/**\n * Load a Stellar contract using the official Stellar SDK approach\n * Based on the patterns from the official Stellar laboratory\n */\nexport async function loadStellarContractFromAddress(\n contractAddress: string,\n networkConfig: StellarNetworkConfig\n): Promise<ContractSchema> {\n logger.info('loadStellarContractFromAddress', 'Loading contract:', {\n contractAddress,\n network: networkConfig.name,\n rpcUrl: networkConfig.sorobanRpcUrl,\n networkPassphrase: networkConfig.networkPassphrase,\n });\n\n try {\n // Validate contract address\n if (!StellarSdk.StrKey.isValidContract(contractAddress)) {\n throw new Error(`Invalid contract address: ${contractAddress}`);\n }\n\n // Special-case: detect SAC and construct spec locally\n try {\n const execType = await getStellarContractType(contractAddress, networkConfig);\n\n if (execType === 'contractExecutableStellarAsset') {\n const { base64Entries, specEntries } = await getSacSpecArtifacts();\n const spec = new StellarSdk.contract.Spec(base64Entries);\n\n const functions = await extractFunctionsFromSpec(\n spec,\n contractAddress,\n specEntries,\n networkConfig\n );\n\n return {\n name: `Stellar Asset Contract ${contractAddress.slice(0, 8)}...`,\n ecosystem: 'stellar',\n functions,\n metadata: {\n specEntries,\n },\n };\n }\n } catch (e) {\n // If detection path fails unexpectedly, fall back to SDK client path below\n logger.warn(\n 'loadStellarContractFromAddress',\n 'SAC detection failed, falling back to regular client:',\n e\n );\n }\n\n // Create contract client using the official Stellar SDK approach\n // Laboratory note: some contracts may be missing Wasm/definition retrievable via RPC.\n // In that case the SDK can throw an internal error like\n // \"Cannot destructure property 'length'...\". Detect and surface an explicit error.\n let contractClient: StellarSdk.contract.Client;\n try {\n contractClient = await StellarSdk.contract.Client.from({\n contractId: contractAddress,\n networkPassphrase: networkConfig.networkPassphrase,\n rpcUrl: networkConfig.sorobanRpcUrl,\n });\n } catch (e) {\n const message = (e as Error)?.message || String(e);\n if (message.includes(\"Cannot destructure property 'length'\")) {\n const friendly =\n 'Unable to fetch contract metadata from RPC. The contract appears to have no published Wasm/definition on this network.';\n logger.error('loadStellarContractFromAddress', friendly);\n throw new Error(`NO_WASM: ${friendly}`);\n }\n throw e;\n }\n\n logger.info('loadStellarContractFromAddress', 'Contract client created successfully');\n\n // Get spec entries - try different approaches to access them\n let specEntries: xdr.ScSpecEntry[] = [];\n try {\n // Access spec entries from the spec object\n\n // Try to access spec entries through different possible properties/methods\n if (contractClient.spec && typeof contractClient.spec === 'object') {\n const spec = contractClient.spec as unknown as Record<string, unknown>;\n\n // Try common property names\n if (Array.isArray(spec.entries)) {\n specEntries = spec.entries as xdr.ScSpecEntry[];\n } else if (Array.isArray(spec._entries)) {\n specEntries = spec._entries as xdr.ScSpecEntry[];\n } else if (Array.isArray(spec.specEntries)) {\n specEntries = spec.specEntries as xdr.ScSpecEntry[];\n } else if (typeof spec.entries === 'function') {\n // Maybe it's a method after all, but with different signature\n try {\n specEntries = (spec.entries as () => xdr.ScSpecEntry[])();\n } catch (e) {\n logger.warn('loadStellarContractFromAddress', 'entries() method failed:', e);\n }\n }\n\n // Try the method directly on spec if it has the method\n if (specEntries.length === 0 && typeof spec.entries === 'function') {\n try {\n specEntries = (spec.entries as () => xdr.ScSpecEntry[])();\n } catch (e) {\n logger.warn('loadStellarContractFromAddress', 'direct entries() method failed:', e);\n }\n }\n\n logger.info('loadStellarContractFromAddress', `Found ${specEntries.length} spec entries`);\n }\n } catch (specError) {\n logger.warn('loadStellarContractFromAddress', 'Could not extract spec entries:', specError);\n }\n\n // Extract functions using the official laboratory approach with spec entries for struct extraction\n const functions = await extractFunctionsFromSpec(\n contractClient.spec,\n contractAddress,\n specEntries,\n networkConfig\n );\n\n logger.info(\n 'loadStellarContractFromAddress',\n `Successfully extracted ${functions.length} functions`\n );\n\n return {\n name: `Soroban Contract ${contractAddress.slice(0, 8)}...`,\n ecosystem: 'stellar',\n functions,\n metadata: {\n specEntries,\n },\n };\n } catch (error) {\n const msg = (error as Error)?.message || String(error);\n // Preserve explicit NO_WASM error so downstream can surface it to the user\n if (msg.startsWith('NO_WASM:')) {\n logger.error('loadStellarContractFromAddress', msg);\n throw new Error(msg);\n }\n logger.error('loadStellarContractFromAddress', 'Failed to load contract:', error);\n throw new Error(`Failed to load contract: ${msg}`);\n }\n}\n\n/**\n * Extract functions from contract spec using the official Stellar laboratory approach\n * with simulation-based state mutability detection\n */\nasync function extractFunctionsFromSpec(\n spec: StellarSdk.contract.Spec,\n contractAddress: string,\n specEntries?: xdr.ScSpecEntry[],\n networkConfig?: StellarNetworkConfig\n): Promise<ContractFunction[]> {\n try {\n // Get all functions using the official SDK method\n const specFunctions = spec.funcs();\n\n logger.info('extractFunctionsFromSpec', `Found ${specFunctions.length} functions in spec`);\n\n return await Promise.all(\n specFunctions.map(async (func, index) => {\n try {\n // Extract function name using the official SDK method\n const functionName = func.name().toString();\n\n logger.info('extractFunctionsFromSpec', `Processing function: ${functionName}`);\n\n // Get function inputs and outputs using the official SDK methods\n const inputs: FunctionParameter[] = func.inputs().map((input, inputIndex) => {\n try {\n const inputName = input.name().toString();\n const inputType = extractSorobanTypeFromScSpec(input.type());\n\n if (inputType === 'unknown') {\n logger.warn(\n 'extractFunctionsFromSpec',\n `Unknown type for parameter \"${inputName}\" in function \"${functionName}\"`\n );\n }\n\n // Check if this is a struct type and extract components\n let components: FunctionParameter[] | undefined;\n if (specEntries && specEntries.length > 0 && isStructType(specEntries, inputType)) {\n const structFields = extractStructFields(specEntries, inputType);\n if (structFields && structFields.length > 0) {\n components = structFields;\n logger.debug(\n 'extractFunctionsFromSpec',\n `Extracted ${structFields.length} fields for struct type \"${inputType}\": ${structFields.map((f) => `${f.name}:${f.type}`).join(', ')}`\n );\n } else {\n logger.warn(\n 'extractFunctionsFromSpec',\n `No fields extracted for struct \"${inputType}\"`\n );\n }\n }\n\n return {\n name: inputName || `param_${inputIndex}`,\n type: inputType,\n ...(components && { components }),\n };\n } catch (error) {\n logger.warn(\n 'extractFunctionsFromSpec',\n `Failed to parse input ${inputIndex}:`,\n error\n );\n return {\n name: `param_${inputIndex}`,\n type: 'unknown',\n };\n }\n });\n\n const outputs: FunctionParameter[] = func.outputs().map((output, outputIndex) => {\n try {\n // Outputs are ScSpecTypeDef objects, they don't have names, only types\n const outputType = extractSorobanTypeFromScSpec(output);\n\n return {\n name: `result_${outputIndex}`,\n type: outputType,\n };\n } catch (error) {\n logger.warn(\n 'extractFunctionsFromSpec',\n `Failed to parse output ${outputIndex}:`,\n error\n );\n return {\n name: `result_${outputIndex}`,\n type: 'unknown',\n };\n }\n });\n\n // Determine if function is read-only (view function) using simulation-based detection\n // This follows the same approach as the official Stellar Laboratory\n let modifiesState = true; // Default assumption for safety\n let stateMutability: 'view' | 'pure' | 'nonpayable' = 'nonpayable';\n\n if (networkConfig) {\n try {\n // Extract input types for simulation\n const inputTypes = inputs.map((input) => input.type);\n\n logger.debug(\n 'extractFunctionsFromSpec',\n `Checking state mutability for ${functionName} with input types: ${inputTypes.join(', ')}`\n );\n\n // Use simulation-based state mutability detection\n modifiesState = await checkStellarFunctionStateMutability(\n contractAddress,\n functionName,\n networkConfig,\n inputTypes\n );\n\n stateMutability = modifiesState ? 'nonpayable' : 'view';\n\n logger.info(\n 'extractFunctionsFromSpec',\n `Function ${functionName} state mutability determined:`,\n { modifiesState, stateMutability }\n );\n } catch (error) {\n logger.warn(\n 'extractFunctionsFromSpec',\n `Failed to determine state mutability for ${functionName}, assuming it modifies state:`,\n error\n );\n // Keep defaults: modifiesState = true, stateMutability = 'nonpayable'\n }\n } else {\n logger.warn(\n 'extractFunctionsFromSpec',\n `No network config provided for ${functionName}, assuming it modifies state`\n );\n }\n\n // Generate a unique ID for the function\n const functionId = `${functionName}_${inputs.map((i) => i.type).join('_')}`;\n\n return {\n id: functionId,\n name: functionName,\n displayName:\n functionName.charAt(0).toUpperCase() + functionName.slice(1).replace(/_/g, ' '),\n description: `Soroban function: ${functionName}`,\n inputs,\n outputs,\n type: 'function',\n modifiesState,\n stateMutability,\n };\n } catch (error) {\n logger.error('extractFunctionsFromSpec', `Failed to process function ${index}:`, error);\n\n // Return a basic function entry for failed parsing\n return {\n id: `function_${index}`,\n name: `function_${index}`,\n displayName: `Function ${index}`,\n description: `Failed to parse function ${index}: ${(error as Error).message}`,\n inputs: [],\n outputs: [],\n type: 'function',\n modifiesState: true,\n stateMutability: 'nonpayable',\n };\n }\n })\n );\n } catch (error) {\n logger.error('extractFunctionsFromSpec', 'Failed to extract functions from spec:', error);\n throw new Error(`Failed to extract functions: ${(error as Error).message}`);\n }\n}\n\n/**\n * Enhanced result type for Stellar contract loading with metadata\n */\nexport interface StellarContractLoadResult {\n schema: ContractSchema;\n source: 'fetched' | 'manual';\n contractDefinitionOriginal?: string;\n metadata?: {\n fetchedFrom?: string;\n contractName?: string;\n fetchTimestamp?: Date;\n definitionHash?: string;\n };\n}\n\n/**\n * Load Stellar contract with basic metadata\n */\nexport async function loadStellarContract(\n artifacts: StellarContractArtifacts,\n networkConfig: StellarNetworkConfig\n): Promise<StellarContractLoadResult> {\n if (typeof artifacts.contractAddress !== 'string') {\n throw new Error('A contract address must be provided.');\n }\n\n const schema = await loadStellarContractFromAddress(artifacts.contractAddress, networkConfig);\n\n const schemaWithAddress = { ...schema, address: artifacts.contractAddress };\n\n return {\n schema: schemaWithAddress,\n source: 'fetched',\n contractDefinitionOriginal: JSON.stringify(schemaWithAddress),\n metadata: {\n fetchedFrom:\n getStellarExplorerAddressUrl(artifacts.contractAddress, networkConfig) ||\n networkConfig.sorobanRpcUrl,\n contractName: schema.name,\n fetchTimestamp: new Date(),\n },\n };\n}\n\n/**\n * Load Stellar contract with extended metadata\n */\nexport async function loadStellarContractWithMetadata(\n artifacts: StellarContractArtifacts,\n networkConfig: StellarNetworkConfig\n): Promise<StellarContractLoadResult> {\n if (typeof artifacts.contractAddress !== 'string') {\n throw new Error('A contract address must be provided.');\n }\n\n try {\n const contractData = await loadStellarContractFromAddress(\n artifacts.contractAddress,\n networkConfig\n );\n\n const schema = {\n ...contractData,\n address: artifacts.contractAddress,\n };\n\n return {\n schema,\n source: 'fetched',\n contractDefinitionOriginal: JSON.stringify(schema),\n metadata: {\n fetchedFrom:\n getStellarExplorerAddressUrl(artifacts.contractAddress, networkConfig) ||\n networkConfig.sorobanRpcUrl,\n contractName: schema.name,\n fetchTimestamp: new Date(),\n },\n };\n } catch (error) {\n // Check if this is a network/connection error\n const errorMessage = (error as Error).message || '';\n // Surface Laboratory-style explicit message if Wasm is missing\n if (errorMessage.startsWith('NO_WASM:')) {\n // Re-throw without swallowing details so UI can show this immediately\n throw new Error(errorMessage.replace(/^NO_WASM:\\s*/, ''));\n }\n if (errorMessage.includes('Failed to load contract')) {\n throw new Error(\n `Contract at ${artifacts.contractAddress} could not be loaded from the network. ` +\n `Please verify the contract ID is correct and the network is accessible.`\n );\n }\n\n // Re-throw other errors\n throw error;\n }\n}\n\n/**\n * Integration points for manual contract definition input (future work):\n *\n * Single Input with Auto-Detection (simplified UX):\n * - Add a new loader path: `loadStellarContractFromDefinition(definition, networkConfig)`\n * - Auto-detect content type using magic bytes and structure:\n * - Wasm binary: starts with magic bytes `[0x00, 0x61, 0x73, 0x6D]` (`\\0asm`)\n * - JSON spec: valid JSON array with Soroban spec entry objects\n * - For JSON: Parse and validate, use `transformStellarSpecToSchema()` to build schema\n * - For Wasm: Extract embedded spec from binary locally (no RPC), then build schema\n * - Return `{ schema, source: 'manual' }` with `contractDefinitionOriginal` set to\n * the raw input (JSON string or Wasm binary) for auto-save restoration\n *\n * The builder UI provides a single input field (code editor with file upload support)\n * that accepts either format, eliminating user confusion about format selection.\n * The auto-save system will store the resulting schema and `contractDefinitionOriginal`\n * so the configuration restores seamlessly.\n */\n\n/**\n * Transform Stellar contract spec to our internal schema format.\n *\n * This function is intentionally minimal at the moment and primarily used by\n * tests. The production load path derives function metadata using the\n * Stellar SDK via `loadStellarContractFromAddress`/`extractFunctionsFromSpec`.\n * A full spec-to-schema converter (with robust type mapping for inputs/outputs\n * and state mutability inference) is planned under the upcoming\n * \"Type Mapping and Data Transformation\" work in\n * `.agent-os/specs/2025-08-20-stellar-adapter-integration/tasks.md`.\n */\nexport function transformStellarSpecToSchema(\n contractSpec: Record<string, unknown>,\n contractAddress: string,\n ecosystem: 'stellar' = 'stellar'\n): ContractSchema {\n logger.info('transformStellarSpecToSchema', 'Transforming Stellar spec to schema format');\n\n const schema: ContractSchema = {\n name: (contractSpec.name as string) || `Soroban Contract ${contractAddress.slice(0, 8)}...`,\n ecosystem,\n functions: (contractSpec.functions as ContractFunction[]) || [],\n };\n\n logger.info('transformStellarSpecToSchema', 'Generated schema:', {\n name: schema.name,\n ecosystem: schema.ecosystem,\n functionCount: Array.isArray(schema.functions) ? schema.functions.length : 0,\n });\n\n return schema;\n}\n","import { StrKey } from '@stellar/stellar-sdk';\n\n/**\n * Stellar address types supported by the validation functions\n */\nexport type StellarAddressType =\n | 'account' // G... - Ed25519 public keys (standard account addresses)\n | 'contract' // C... - Contract addresses\n | 'muxed' // M... - Muxed account addresses (for exchanges/sub-accounts)\n | 'secret' // S... - Secret seeds (private keys)\n | 'signed-payload' // P... - Signed payload addresses\n | 'pre-auth-tx' // T... - Pre-authorized transaction hashes\n | 'hash-x'; // X... - Hash-x condition addresses\n\n/**\n * Validate a standard Stellar account address (Ed25519 public key starting with 'G')\n * @param address The address to validate\n * @returns Whether the address is a valid account address\n */\nexport function isValidAccountAddress(address: string): boolean {\n try {\n return StrKey.isValidEd25519PublicKey(address);\n } catch {\n return false;\n }\n}\n\n/**\n * Validate a Stellar contract address (starting with 'C')\n * @param address The address to validate\n * @returns Whether the address is a valid contract address\n */\nexport function isValidContractAddress(address: string): boolean {\n try {\n return StrKey.isValidContract(address);\n } catch {\n return false;\n }\n}\n\n/**\n * Validate a Stellar muxed account address (starting with 'M')\n * @param address The address to validate\n * @returns Whether the address is a valid muxed account address\n */\nexport function isValidMuxedAddress(address: string): boolean {\n try {\n return StrKey.isValidMed25519PublicKey(address);\n } catch {\n return false;\n }\n}\n\n/**\n * Validate a Stellar secret seed (private key starting with 'S')\n * @param seed The secret seed to validate\n * @returns Whether the seed is a valid secret seed\n */\nexport function isValidSecretSeed(seed: string): boolean {\n try {\n return StrKey.isValidEd25519SecretSeed(seed);\n } catch {\n return false;\n }\n}\n\n/**\n * Validate a signed payload address (starting with 'P')\n * @param address The address to validate\n * @returns Whether the address is a valid signed payload address\n */\nexport function isValidSignedPayloadAddress(address: string): boolean {\n try {\n return StrKey.isValidSignedPayload(address);\n } catch {\n return false;\n }\n}\n\n/**\n * Main validation function that supports all Stellar address types\n * @param address The address to validate\n * @param addressType Optional specific address type to validate\n * @returns Whether the address is valid for the specified or any supported type\n */\nexport function isValidAddress(address: string, addressType?: StellarAddressType): boolean {\n if (!address || typeof address !== 'string') {\n return false;\n }\n\n // If specific type is requested, validate only that type\n if (addressType) {\n switch (addressType) {\n case 'account':\n return isValidAccountAddress(address);\n case 'contract':\n return isValidContractAddress(address);\n case 'muxed':\n return isValidMuxedAddress(address);\n case 'secret':\n return isValidSecretSeed(address);\n case 'signed-payload':\n return isValidSignedPayloadAddress(address);\n case 'pre-auth-tx':\n try {\n // Pre-auth transactions start with 'T' - validate by trying to decode\n StrKey.decodePreAuthTx(address);\n return true;\n } catch {\n return false;\n }\n case 'hash-x':\n try {\n // Hash-x conditions start with 'X' - validate by trying to decode\n StrKey.decodeSha256Hash(address);\n return true;\n } catch {\n return false;\n }\n default:\n return false;\n }\n }\n\n // If no specific type requested, validate against common address types\n // (excluding secrets and special-purpose addresses for security)\n try {\n return (\n StrKey.isValidEd25519PublicKey(address) || // G... - accounts (most common)\n StrKey.isValidContract(address) || // C... - contracts\n StrKey.isValidMed25519PublicKey(address) // M... - muxed accounts\n );\n } catch {\n return false;\n }\n}\n","import { EoaExecutionConfig } from '@openzeppelin/ui-builder-types';\nimport { logger } from '@openzeppelin/ui-builder-utils';\n\nimport { StellarWalletConnectionStatus } from '../wallet/types';\nimport { isValidAddress } from './address';\n\nconst SYSTEM_LOG_TAG = 'StellarEoaValidator';\n\nexport async function validateEoaConfig(\n config: EoaExecutionConfig,\n walletStatus: StellarWalletConnectionStatus\n): Promise<true | string> {\n if (!config.allowAny) {\n if (!config.specificAddress) {\n return \"EOA execution selected, but no specific address was provided when 'allowAny' is false.\";\n }\n if (!isValidAddress(config.specificAddress)) {\n return `Invalid specific Stellar address format: ${config.specificAddress}`;\n }\n if (walletStatus.isConnected && walletStatus.address) {\n if (walletStatus.address !== config.specificAddress) {\n return `Connected wallet address (${walletStatus.address}) does not match the required specific Stellar address (${config.specificAddress}). Please connect the correct wallet.`;\n }\n } else if (walletStatus.isConnected && !walletStatus.address) {\n logger.warn(\n SYSTEM_LOG_TAG,\n 'Wallet is connected but address is unavailable for Stellar EOA validation.'\n );\n return 'Connected wallet address is not available for validation against specific Stellar address.';\n }\n }\n return true;\n}\n","import { RelayerExecutionConfig } from '@openzeppelin/ui-builder-types';\n\nexport async function validateRelayerConfig(\n config: RelayerExecutionConfig\n): Promise<true | string> {\n if (!config.serviceUrl) {\n return 'Relayer execution selected, but no service URL was provided.';\n }\n if (!config.relayer?.relayerId) {\n return 'Relayer execution selected, but no relayer was chosen from the list.';\n }\n return true;\n}\n","/*\n * Stellar Explorer Configuration\n *\n * DESIGN NOTE: This module provides minimal explorer functionality compared to EVM adapters.\n * Stellar explorers are used only for generating display URLs, unlike EVM where explorers\n * are critical infrastructure for ABI fetching. See comments below for detailed explanation.\n */\n\nimport { NetworkConfig } from '@openzeppelin/ui-builder-types';\nimport type { UserExplorerConfig } from '@openzeppelin/ui-builder-types';\n\nimport { isValidContractAddress } from '../validation';\n\n/**\n * Gets a blockchain explorer URL for an address on Stellar.\n * Uses the explorerUrl from the network configuration.\n *\n * @param address The address to get the explorer URL for\n * @param networkConfig The network configuration object.\n * @returns A URL to view the address on the configured Stellar explorer, or null.\n */\nexport function getStellarExplorerAddressUrl(\n address: string,\n networkConfig: NetworkConfig\n): string | null {\n if (!address || !networkConfig.explorerUrl) {\n return null;\n }\n // Use /contract for Soroban contract IDs, otherwise /account\n const baseUrl = networkConfig.explorerUrl.replace(/\\/+$/, '');\n const path = isValidContractAddress(address) ? 'contract' : 'account';\n return `${baseUrl}/${path}/${encodeURIComponent(address)}`;\n}\n\n/**\n * Gets a blockchain explorer URL for a transaction on Stellar.\n * Uses the explorerUrl from the network configuration.\n *\n * @param txHash - The hash of the transaction to get the explorer URL for\n * @param networkConfig The network configuration object.\n * @returns A URL to view the transaction on the configured Stellar explorer, or null.\n */\nexport function getStellarExplorerTxUrl(\n txHash: string,\n networkConfig: NetworkConfig\n): string | null {\n if (!txHash || !networkConfig.explorerUrl) {\n return null;\n }\n // Construct the URL, assuming a standard /tx/ path for Stellar explorers\n const baseUrl = networkConfig.explorerUrl.replace(/\\/+$/, '');\n return `${baseUrl}/tx/${encodeURIComponent(txHash)}`;\n}\n\n/**\n * Validates a Stellar explorer configuration.\n *\n * NOTE: This validation is minimal compared to EVM - only checks URL formats.\n * No API key validation or connection testing since Stellar explorers are\n * display-only (not used for contract ABI fetching like in EVM).\n */\nexport function validateStellarExplorerConfig(explorerConfig: UserExplorerConfig): boolean {\n // Validate URLs if provided\n if (explorerConfig.explorerUrl) {\n try {\n new URL(explorerConfig.explorerUrl);\n } catch {\n return false;\n }\n } else {\n // explorerUrl is required\n return false;\n }\n\n if (explorerConfig.apiUrl) {\n try {\n new URL(explorerConfig.apiUrl);\n } catch {\n return false;\n }\n }\n\n // Basic API key validation (not empty)\n if (explorerConfig.apiKey !== undefined && explorerConfig.apiKey.trim().length === 0) {\n return false;\n }\n\n return true;\n}\n\n/*\n * NOTE: Unlike EVM adapters, Stellar does not implement explorer connection testing.\n *\n * DESIGN DECISION: Stellar explorers are used only for generating display URLs,\n * not for critical functionality like ABI fetching (which EVM requires).\n *\n * Key differences from EVM:\n * - EVM: Explorers provide essential APIs for contract ABI fetching\n * - Stellar: Explorers are display-only; contract loading uses Soroban RPC directly\n * - EVM: Multiple explorer providers with varying API formats requiring validation\n * - Stellar: Standardized ecosyst