import Conf from 'conf' import type { Config, ChainConfig } from '../types/config.js' import { DEFAULT_CHAINS } from '../constants/chains.js' export class ConfigStore { private store: Conf constructor(options?: { cwd?: string; projectName?: string }) { this.store = new Conf({ projectName: options?.projectName || 'safe-cli', cwd: options?.cwd, defaults: { version: '0.1.0', chains: DEFAULT_CHAINS, defaults: { safeVersion: '1.4.1', signingMethod: 'ETH_SIGN_TYPED_DATA_V4', gasStrategy: 'medium', }, preferences: { isStagingSafeApi: false, }, }, }) } // Get entire config getConfig(): Config { return this.store.store } // Chain management getChain(chainId: string): ChainConfig | undefined { // First try to get custom/override chain from store const customChain = this.store.get(`chains.${chainId}`) as ChainConfig | undefined if (customChain && customChain.chainId) { return customChain } // Fallback to default chain if available return DEFAULT_CHAINS[chainId] } getAllChains(): Record { // Get custom chains from store const customChains = this.store.get('chains', {}) // Merge DEFAULT_CHAINS with custom chains // Custom chains override defaults if same chainId exists return { ...DEFAULT_CHAINS, ...customChains, } } setChain(chainId: string, config: ChainConfig): void { this.store.set(`chains.${chainId}`, config) } deleteChain(chainId: string): void { // Only delete if it's a custom chain or override in the store const customChains = this.store.get('chains', {}) if (customChains[chainId]) { delete customChains[chainId] this.store.set('chains', customChains) } // If chain only exists in DEFAULT_CHAINS, do nothing (can't delete defaults) } chainExists(chainId: string): boolean { return this.getChain(chainId) !== undefined } /** * Check if a chain is a custom chain (not from defaults) * @returns true if chain is custom/override, false if it's a default chain */ isCustomChain(chainId: string): boolean { const customChains = this.store.get('chains', {}) return customChains[chainId] !== undefined } /** * Get default chain for balance checks and other operations * Returns Ethereum mainnet (chainId: '1') by default, or first available chain */ getDefaultChain(): ChainConfig { // Try to get Ethereum mainnet first const ethMainnet = this.getChain('1') if (ethMainnet) { return ethMainnet } // Fallback to first available chain const chains = this.getAllChains() const firstChainId = Object.keys(chains)[0] if (firstChainId) { return chains[firstChainId] } // If no chains configured (shouldn't happen), return a default return DEFAULT_CHAINS['1'] } // Defaults getDefaults() { return this.store.get('defaults') } setDefault(key: K, value: Config['defaults'][K]): void { this.store.set(`defaults.${key}`, value) } // Preferences getPreferences() { return this.store.get('preferences') } setPreference( key: K, value: Config['preferences'][K] ): void { this.store.set(`preferences.${key}`, value) } // Staging mode helpers isStagingMode(): boolean { return this.store.get('preferences.isStagingSafeApi', false) } setStagingMode(enabled: boolean): void { this.store.set('preferences.isStagingSafeApi', enabled) } // Reset to defaults reset(): void { this.store.clear() } // Get config file path getConfigPath(): string { return this.store.path } } // Singleton instance let configStore: ConfigStore | null = null export function getConfigStore(): ConfigStore { if (!configStore) { configStore = new ConfigStore() } return configStore }