import { storeJson } from '../fileModels/store.json' import { i18n } from '../i18n' import { sdk } from '../sdk' import { defaultStratumPort, defaultStratumTlsPort, logLevels, validatePorts, } from '../utils' const { InputSpec, Value } = sdk export const inputSpec = InputSpec.of({ stratumPort: Value.number({ name: i18n('Stratum Port'), description: i18n( 'TCP port the plaintext stratum server listens on. StartOS also tries to publish the pool on this same port number on your network, so this is normally the port you give your miners — check the Stratum interface after saving, since the OS will pick a different external port if this one is already taken.', ), required: true, default: defaultStratumPort, integer: true, min: 1, max: 65535, }), stratumTlsPort: Value.number({ name: i18n('Stratum TLS Port'), description: i18n( 'TCP port stunnel accepts TLS stratum connections on. Only used when Stratum TLS is enabled below.', ), required: true, default: defaultStratumTlsPort, integer: true, min: 1, max: 65535, }), coinbaseTag: Value.text({ name: i18n('Coinbase Tag'), description: i18n( 'Short string embedded in the coinbase transaction of solved blocks.', ), required: true, default: '/Kamado/', patterns: [], }), zmqEnabled: Value.toggle({ name: i18n('ZMQ Block Notifications'), description: i18n( "Subscribe to Bitcoin Core's hashblock ZMQ topic for sub-second block detection. RPC polling remains active as a fallback either way.", ), default: true, }), tlsEnabled: Value.toggle({ name: i18n('Stratum TLS'), description: i18n( 'Accept stratum connections over TLS via an stunnel sidecar on a second port. A self-signed certificate is generated once and persisted — miners must trust the certificate (see the Stratum TLS Certificate action) or connect with verification disabled.', ), default: false, }), startDiff: Value.number({ name: i18n('Starting Difficulty'), description: i18n( 'Initial vardiff target for new miner connections. Bitaxe-class miners typically land around 16384.', ), required: true, default: 16384, integer: true, min: 1, }), minDiff: Value.number({ name: i18n('Minimum Difficulty'), description: i18n('Floor for the vardiff algorithm.'), required: true, default: 1000, integer: true, min: 1, }), maxDiff: Value.number({ name: i18n('Maximum Difficulty'), description: i18n('Ceiling for the vardiff algorithm. 0 means no cap.'), required: true, default: 0, integer: true, min: 0, }), dropIdle: Value.number({ name: i18n('Drop Idle (seconds)'), description: i18n( 'Disconnect clients that have not submitted a share in this many seconds. 0 disables the idle disconnect.', ), required: true, default: 0, integer: true, min: 0, units: i18n('seconds'), }), logLevel: Value.select({ name: i18n('Log Level'), description: i18n('Verbosity of the kamado-api log output.'), values: logLevels, default: 'info', }), mempoolExplorerUrl: Value.text({ name: i18n('Custom Block Explorer URL'), description: i18n( 'Base URL of a self-hosted mempool instance for dashboard links (e.g. https://mempool.example.com). Kamado appends /address/ and /block/, so the instance must follow the standard mempool.space URL layout. Leave empty to use the public mempool.space.', ), required: false, default: null, patterns: [ { regex: '^https?://[^\\s]+$', description: i18n( 'Must be an http:// or https:// URL with no whitespace', ), }, ], }), }) export const config = sdk.Action.withInput( // id 'config', // metadata async ({ effects }) => ({ name: i18n('Configure'), description: i18n( 'Customize vardiff, TLS, block notifications, logging, and explorer links', ), warning: null, allowedStatuses: 'any', group: null, visibility: 'enabled', }), // form input specification inputSpec, // optionally pre-fill the input form async ({ effects }) => storeJson.read().once(), // the execution function async ({ effects, input }) => { // Refuse port choices that cannot bind — all of these processes share one // network namespace, so a collision would surface as a restart loop after // saving rather than as an error here. const conflict = validatePorts({ stratumPort: input.stratumPort, stratumTlsPort: input.stratumTlsPort, tlsEnabled: input.tlsEnabled, }) if (conflict) throw new Error(conflict) return storeJson.merge(effects, input) }, )