Module jmcore.settings

Unified settings management for JoinMarket components.

This module provides a centralized configuration system using pydantic-settings that supports: 1. TOML configuration file (~/.joinmarket-ng/config.toml) 2. Environment variables 3. CLI arguments (via typer, handled by components)

Priority (highest to lowest): 1. CLI arguments 2. Environment variables 3. Config file 4. Default values

The config file is auto-generated on first run with all settings commented out, allowing users to selectively override only the settings they want to change. This approach facilitates software updates since unchanged defaults can be updated without user intervention.

Usage

from jmcore.settings import get_settings, JoinMarketSettings

Get settings (loads from all sources with proper priority)

settings = get_settings()

Access common settings

print(settings.tor.socks_host) print(settings.bitcoin.rpc_url)

Environment Variable Naming: - Use uppercase with double underscore for nested settings - Examples: TOR__SOCKS_HOST, BITCOIN__RPC_URL, MAKER__MIN_SIZE - Maps to TOML sections: TOR__SOCKS_HOST -> [tor] socks_host

Functions

def ensure_config_file(data_dir: Path | None = None) ‑> pathlib.Path
Expand source code
def ensure_config_file(data_dir: Path | None = None) -> Path:
    """
    Ensure the config file exists, creating a template if it doesn't.

    Args:
        data_dir: Optional data directory path. Uses default if not provided.

    Returns:
        Path to the config file.
    """
    if data_dir is None:
        data_dir = get_default_data_dir()

    config_path = data_dir / "config.toml"

    if not config_path.exists():
        logger.info(f"Creating config file template at {config_path}")
        data_dir.mkdir(parents=True, exist_ok=True)
        config_path.write_text(generate_config_template())

    return config_path

Ensure the config file exists, creating a template if it doesn't.

Args

data_dir
Optional data directory path. Uses default if not provided.

Returns

Path to the config file.

def generate_config_template() ‑> str
Expand source code
def generate_config_template() -> str:
    """
    Generate a config file template with all settings commented out.

    This allows users to see all available settings with their defaults
    and descriptions, while only uncommenting what they want to change.
    """
    lines: list[str] = []

    lines.append("# JoinMarket NG Configuration")
    lines.append("#")
    lines.append("# This file contains all available settings with their default values.")
    lines.append("# Settings are commented out by default - uncomment to override.")
    lines.append("#")
    lines.append("# Priority (highest to lowest):")
    lines.append("#   1. CLI arguments")
    lines.append("#   2. Environment variables")
    lines.append("#   3. This config file")
    lines.append("#   4. Built-in defaults")
    lines.append("#")
    lines.append("# Environment variables use uppercase with double underscore for nesting:")
    lines.append("#   TOR__SOCKS_HOST=127.0.0.1")
    lines.append("#   BITCOIN__RPC_URL=http://localhost:8332")
    lines.append("#")
    lines.append("")

    # Generate sections for each nested model
    def add_section(title: str, model_cls: type[BaseModel], prefix: str = "") -> None:
        lines.append(f"# {'=' * 60}")
        lines.append(f"# {title}")
        lines.append(f"# {'=' * 60}")
        lines.append(f"[{prefix}]" if prefix else "")
        lines.append("")

        for field_name, field_info in model_cls.model_fields.items():
            # Get description
            desc = field_info.description or ""
            if desc:
                lines.append(f"# {desc}")

            # Get default value
            default = field_info.default
            factory = field_info.default_factory
            if factory is not None:
                # default_factory can be Callable[[], Any] or Callable[[dict], Any]
                # We call with no args for the common case
                try:
                    default = factory()  # type: ignore[call-arg]
                except TypeError:
                    default = factory({})  # type: ignore[call-arg]

            # Format the value for TOML
            if isinstance(default, bool):
                value_str = str(default).lower()
            elif isinstance(default, str):
                value_str = f'"{default}"'
            elif isinstance(default, list):
                # For directory_servers, show example from defaults
                if field_name == "directory_servers" and prefix == "network_config":
                    lines.append("# directory_servers = [")
                    for server in DEFAULT_DIRECTORY_SERVERS["mainnet"]:
                        lines.append(f'#   "{server}",')
                    lines.append("# ]")
                    lines.append("")
                    continue
                value_str = "[]" if not default else str(default).replace("'", '"')
            elif isinstance(default, SecretStr):
                value_str = '""'
            elif default is None:
                # Skip None values with a comment
                lines.append(f"# {field_name} = ")
                lines.append("")
                continue
            elif hasattr(default, "value"):  # Enum - use string value
                value_str = f'"{default.value}"'
            else:
                value_str = str(default)

            lines.append(f"# {field_name} = {value_str}")
            lines.append("")

    # Data directory (top-level)
    lines.append("# Data directory for JoinMarket files")
    lines.append("# Defaults to ~/.joinmarket-ng or $JOINMARKET_DATA_DIR")
    lines.append("# data_dir = ")
    lines.append("")

    # Add all sections
    add_section("Tor Settings", TorSettings, "tor")
    add_section("Bitcoin Backend Settings", BitcoinSettings, "bitcoin")
    add_section("Network Settings", NetworkSettings, "network_config")
    add_section("Wallet Settings", WalletSettings, "wallet")
    add_section("Notification Settings", NotificationSettings, "notifications")
    add_section("Logging Settings", LoggingSettings, "logging")
    add_section("Maker Settings", MakerSettings, "maker")
    add_section("Taker Settings", TakerSettings, "taker")
    add_section("Directory Server Settings", DirectoryServerSettings, "directory_server")
    add_section("Orderbook Watcher Settings", OrderbookWatcherSettings, "orderbook_watcher")

    return "\n".join(lines)

Generate a config file template with all settings commented out.

This allows users to see all available settings with their defaults and descriptions, while only uncommenting what they want to change.

def get_config_path() ‑> pathlib.Path
Expand source code
def get_config_path() -> Path:
    """Get the path to the config file."""
    data_dir_env = os.environ.get("JOINMARKET_DATA_DIR")
    data_dir = Path(data_dir_env) if data_dir_env else Path.home() / ".joinmarket-ng"
    return data_dir / "config.toml"

Get the path to the config file.

def get_settings(**overrides: Any) ‑> JoinMarketSettings
Expand source code
def get_settings(**overrides: Any) -> JoinMarketSettings:
    """
    Get the JoinMarket settings instance.

    On first call, loads settings from all sources. Subsequent calls
    return the cached instance unless reset_settings() is called.

    Args:
        **overrides: Optional settings overrides (highest priority)

    Returns:
        JoinMarketSettings instance
    """
    global _settings
    if _settings is None or overrides:
        _settings = JoinMarketSettings(**overrides)
    return _settings

Get the JoinMarket settings instance.

On first call, loads settings from all sources. Subsequent calls return the cached instance unless reset_settings() is called.

Args

**overrides
Optional settings overrides (highest priority)

Returns

JoinMarketSettings instance

def reset_settings() ‑> None
Expand source code
def reset_settings() -> None:
    """Reset the global settings instance (useful for testing)."""
    global _settings
    _settings = None

Reset the global settings instance (useful for testing).

Classes

class BitcoinSettings (**data: Any)
Expand source code
class BitcoinSettings(BaseModel):
    """Bitcoin backend configuration."""

    backend_type: str = Field(
        default="descriptor_wallet",
        description="Backend type: scantxoutset, descriptor_wallet, or neutrino",
    )
    rpc_url: str = Field(
        default="http://127.0.0.1:8332",
        description="Bitcoin Core RPC URL",
    )
    rpc_user: str = Field(
        default="",
        description="Bitcoin Core RPC username",
    )
    rpc_password: SecretStr = Field(
        default=SecretStr(""),
        description="Bitcoin Core RPC password",
    )
    neutrino_url: str = Field(
        default="http://127.0.0.1:8334",
        description="Neutrino REST API URL (for neutrino backend)",
    )

Bitcoin backend configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var backend_type : str

The type of the None singleton.

var model_config

The type of the None singleton.

var neutrino_url : str

The type of the None singleton.

var rpc_password : pydantic.types.SecretStr

The type of the None singleton.

var rpc_url : str

The type of the None singleton.

var rpc_user : str

The type of the None singleton.

class DirectoryServerSettings (**data: Any)
Expand source code
class DirectoryServerSettings(BaseModel):
    """Directory server specific settings."""

    host: str = Field(
        default="127.0.0.1",
        description="Host address to bind to",
    )
    port: int = Field(
        default=5222,
        ge=0,
        le=65535,
        description="Port to listen on (0 = let OS assign)",
    )
    max_peers: int = Field(
        default=10000,
        ge=1,
        description="Maximum number of connected peers",
    )
    max_message_size: int = Field(
        default=2097152,
        ge=1024,
        description="Maximum message size in bytes (2MB default)",
    )
    max_line_length: int = Field(
        default=65536,
        ge=1024,
        description="Maximum JSON-line message length (64KB default)",
    )
    max_json_nesting_depth: int = Field(
        default=10,
        ge=1,
        description="Maximum nesting depth for JSON parsing",
    )
    message_rate_limit: int = Field(
        default=500,
        ge=1,
        description="Messages per second (sustained)",
    )
    message_burst_limit: int = Field(
        default=1000,
        ge=1,
        description="Maximum burst size",
    )
    rate_limit_disconnect_threshold: int = Field(
        default=0,
        ge=0,
        description="Disconnect after N rate limit violations (0 = never disconnect)",
    )
    broadcast_batch_size: int = Field(
        default=50,
        ge=1,
        description="Batch size for concurrent broadcasts",
    )
    health_check_host: str = Field(
        default="127.0.0.1",
        description="Host for health check endpoint",
    )
    health_check_port: int = Field(
        default=8080,
        ge=0,
        le=65535,
        description="Port for health check endpoint (0 = let OS assign)",
    )
    motd: str = Field(
        default="JoinMarket NG Directory Server https://github.com/m0wer/joinmarket-ng/",
        description="Message of the day sent to clients",
    )

Directory server specific settings.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var broadcast_batch_size : int

The type of the None singleton.

var health_check_host : str

The type of the None singleton.

var health_check_port : int

The type of the None singleton.

var host : str

The type of the None singleton.

var max_json_nesting_depth : int

The type of the None singleton.

var max_line_length : int

The type of the None singleton.

var max_message_size : int

The type of the None singleton.

var max_peers : int

The type of the None singleton.

var message_burst_limit : int

The type of the None singleton.

var message_rate_limit : int

The type of the None singleton.

var model_config

The type of the None singleton.

var motd : str

The type of the None singleton.

var port : int

The type of the None singleton.

var rate_limit_disconnect_threshold : int

The type of the None singleton.

class JoinMarketSettings (**values: Any)
Expand source code
class JoinMarketSettings(BaseSettings):
    """
    Main JoinMarket settings class.

    Loads configuration from multiple sources with the following priority:
    1. CLI arguments (not handled here, passed to component __init__)
    2. Environment variables
    3. TOML config file (~/.joinmarket-ng/config.toml)
    4. Default values
    """

    model_config = SettingsConfigDict(
        env_prefix="",  # No prefix by default, use env_nested_delimiter for nested
        env_nested_delimiter="__",
        case_sensitive=False,
        extra="ignore",  # Ignore unknown fields (for forward compatibility)
    )

    # Marker for config file path discovery
    _config_file_path: ClassVar[Path | None] = None

    # Core settings
    data_dir: Path | None = Field(
        default=None,
        description="Data directory (defaults to ~/.joinmarket-ng)",
    )

    # Nested settings groups
    tor: TorSettings = Field(default_factory=TorSettings)
    bitcoin: BitcoinSettings = Field(default_factory=BitcoinSettings)
    network_config: NetworkSettings = Field(default_factory=NetworkSettings)
    wallet: WalletSettings = Field(default_factory=WalletSettings)
    notifications: NotificationSettings = Field(default_factory=NotificationSettings)
    logging: LoggingSettings = Field(default_factory=LoggingSettings)

    # Component-specific settings
    maker: MakerSettings = Field(default_factory=MakerSettings)
    taker: TakerSettings = Field(default_factory=TakerSettings)
    directory_server: DirectoryServerSettings = Field(default_factory=DirectoryServerSettings)
    orderbook_watcher: OrderbookWatcherSettings = Field(default_factory=OrderbookWatcherSettings)

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """
        Customize settings sources and their priority.

        Priority (highest to lowest):
        1. init_settings (CLI arguments passed to constructor)
        2. env_settings (environment variables with __ delimiter)
        3. toml_settings (config.toml file)
        4. defaults (in field definitions)
        """
        toml_source = TomlConfigSettingsSource(settings_cls)
        return (
            init_settings,
            env_settings,
            toml_source,
        )

    def get_data_dir(self) -> Path:
        """Get the data directory, using default if not set."""
        if self.data_dir is not None:
            return self.data_dir
        return get_default_data_dir()

    def get_directory_servers(self) -> list[str]:
        """Get directory servers, using network defaults if not set."""
        if self.network_config.directory_servers:
            return self.network_config.directory_servers
        network_name = self.network_config.network.value
        return DEFAULT_DIRECTORY_SERVERS.get(network_name, [])

Main JoinMarket settings class.

Loads configuration from multiple sources with the following priority: 1. CLI arguments (not handled here, passed to component init) 2. Environment variables 3. TOML config file (~/.joinmarket-ng/config.toml) 4. Default values

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic_settings.main.BaseSettings
  • pydantic.main.BaseModel

Class variables

var bitcoinBitcoinSettings

The type of the None singleton.

var data_dir : pathlib.Path | None

The type of the None singleton.

var directory_serverDirectoryServerSettings

The type of the None singleton.

var loggingLoggingSettings

The type of the None singleton.

var makerMakerSettings

The type of the None singleton.

var model_config : ClassVar[pydantic_settings.main.SettingsConfigDict]

The type of the None singleton.

var network_configNetworkSettings

The type of the None singleton.

var notificationsNotificationSettings

The type of the None singleton.

var orderbook_watcherOrderbookWatcherSettings

The type of the None singleton.

var takerTakerSettings

The type of the None singleton.

var torTorSettings

The type of the None singleton.

var walletWalletSettings

The type of the None singleton.

Static methods

def settings_customise_sources(settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource) ‑> tuple[pydantic_settings.sources.base.PydanticBaseSettingsSource, ...]

Customize settings sources and their priority.

Priority (highest to lowest): 1. init_settings (CLI arguments passed to constructor) 2. env_settings (environment variables with __ delimiter) 3. toml_settings (config.toml file) 4. defaults (in field definitions)

Methods

def get_data_dir(self) ‑> pathlib.Path
Expand source code
def get_data_dir(self) -> Path:
    """Get the data directory, using default if not set."""
    if self.data_dir is not None:
        return self.data_dir
    return get_default_data_dir()

Get the data directory, using default if not set.

def get_directory_servers(self) ‑> list[str]
Expand source code
def get_directory_servers(self) -> list[str]:
    """Get directory servers, using network defaults if not set."""
    if self.network_config.directory_servers:
        return self.network_config.directory_servers
    network_name = self.network_config.network.value
    return DEFAULT_DIRECTORY_SERVERS.get(network_name, [])

Get directory servers, using network defaults if not set.

class LoggingSettings (**data: Any)
Expand source code
class LoggingSettings(BaseModel):
    """Logging configuration."""

    level: str = Field(
        default="INFO",
        description="Log level: TRACE, DEBUG, INFO, WARNING, ERROR",
    )
    sensitive: bool = Field(
        default=False,
        description="Enable sensitive logging (mnemonics, keys)",
    )

Logging configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var level : str

The type of the None singleton.

var model_config

The type of the None singleton.

var sensitive : bool

The type of the None singleton.

class MakerSettings (**data: Any)
Expand source code
class MakerSettings(BaseModel):
    """Maker-specific settings."""

    min_size: int = Field(
        default=100000,
        ge=0,
        description="Minimum CoinJoin amount in satoshis",
    )
    offer_type: str = Field(
        default="sw0reloffer",
        description="Offer type: sw0reloffer (relative) or sw0absoffer (absolute)",
    )
    cj_fee_relative: str = Field(
        default="0.001",
        description="Relative CoinJoin fee (0.001 = 0.1%)",
    )
    cj_fee_absolute: int = Field(
        default=500,
        ge=0,
        description="Absolute CoinJoin fee in satoshis",
    )
    tx_fee_contribution: int = Field(
        default=0,
        ge=0,
        description="Transaction fee contribution in satoshis",
    )
    min_confirmations: int = Field(
        default=1,
        ge=0,
        description="Minimum confirmations for UTXOs",
    )
    merge_algorithm: str = Field(
        default="default",
        description="UTXO selection: default, gradual, greedy, random",
    )
    session_timeout_sec: int = Field(
        default=300,
        ge=60,
        description="Maximum time for a CoinJoin session",
    )
    pending_tx_timeout_min: int = Field(
        default=60,
        ge=10,
        le=1440,
        description="Minutes before marking unbroadcast CoinJoins as failed",
    )
    rescan_interval_sec: int = Field(
        default=600,
        ge=60,
        description="Interval for periodic wallet rescans",
    )
    # Hidden service settings
    onion_serving_host: str = Field(
        default="127.0.0.1",
        description="Bind address for incoming connections",
    )
    onion_serving_port: int = Field(
        default=5222,
        ge=0,
        le=65535,
        description="Port for incoming onion connections",
    )
    # Rate limiting
    message_rate_limit: int = Field(
        default=10,
        ge=1,
        description="Messages per second per peer (sustained)",
    )
    message_burst_limit: int = Field(
        default=100,
        ge=1,
        description="Maximum burst messages per peer",
    )

    @field_validator("cj_fee_relative", mode="before")
    @classmethod
    def normalize_cj_fee_relative(cls, v: str | float | int) -> str:
        """
        Normalize cj_fee_relative to avoid scientific notation.

        Pydantic may coerce float values (from env vars, TOML, or JSON) to strings,
        which can result in scientific notation for small values (e.g., 1e-05).
        The JoinMarket protocol expects decimal notation (e.g., 0.00001).
        """
        if isinstance(v, (int, float)):
            # Use Decimal to preserve precision and avoid scientific notation
            return format(Decimal(str(v)), "f")
        # Already a string - check if it contains scientific notation
        if "e" in v.lower():
            try:
                return format(Decimal(v), "f")
            except InvalidOperation:
                pass  # Let pydantic handle the validation error
        return v

Maker-specific settings.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var cj_fee_absolute : int

The type of the None singleton.

var cj_fee_relative : str

The type of the None singleton.

var merge_algorithm : str

The type of the None singleton.

var message_burst_limit : int

The type of the None singleton.

var message_rate_limit : int

The type of the None singleton.

var min_confirmations : int

The type of the None singleton.

var min_size : int

The type of the None singleton.

var model_config

The type of the None singleton.

var offer_type : str

The type of the None singleton.

var onion_serving_host : str

The type of the None singleton.

var onion_serving_port : int

The type of the None singleton.

var pending_tx_timeout_min : int

The type of the None singleton.

var rescan_interval_sec : int

The type of the None singleton.

var session_timeout_sec : int

The type of the None singleton.

var tx_fee_contribution : int

The type of the None singleton.

Static methods

def normalize_cj_fee_relative(v: str | float | int) ‑> str

Normalize cj_fee_relative to avoid scientific notation.

Pydantic may coerce float values (from env vars, TOML, or JSON) to strings, which can result in scientific notation for small values (e.g., 1e-05). The JoinMarket protocol expects decimal notation (e.g., 0.00001).

class NetworkSettings (**data: Any)
Expand source code
class NetworkSettings(BaseModel):
    """Network configuration."""

    network: NetworkType = Field(
        default=NetworkType.MAINNET,
        description="JoinMarket protocol network (mainnet, testnet, signet, regtest)",
    )
    bitcoin_network: NetworkType | None = Field(
        default=None,
        description="Bitcoin network for address generation (defaults to network)",
    )
    directory_servers: list[str] = Field(
        default_factory=list,
        description="Directory server addresses (host:port). Uses defaults if empty.",
    )

Network configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var bitcoin_networkNetworkType | None

The type of the None singleton.

var directory_servers : list[str]

The type of the None singleton.

var model_config

The type of the None singleton.

var networkNetworkType

The type of the None singleton.

class NotificationSettings (**data: Any)
Expand source code
class NotificationSettings(BaseModel):
    """Notification system configuration."""

    enabled: bool = Field(
        default=False,
        description="Enable notifications (requires urls to be set)",
    )
    urls: list[str] = Field(
        default_factory=list,
        description='Apprise notification URLs (e.g., ["tgram://bottoken/ChatID", "gotify://hostname/token"])',
    )
    title_prefix: str = Field(
        default="JoinMarket NG",
        description="Prefix for notification titles",
    )
    component_name: str = Field(
        default="",
        description="Component name in notification titles (e.g., 'Maker', 'Taker'). "
        "Usually set programmatically by each component.",
    )
    include_amounts: bool = Field(
        default=True,
        description="Include amounts in notifications",
    )
    include_txids: bool = Field(
        default=False,
        description="Include transaction IDs in notifications (privacy risk)",
    )
    include_nick: bool = Field(
        default=True,
        description="Include peer nicks in notifications",
    )
    use_tor: bool = Field(
        default=True,
        description="Route notifications through Tor SOCKS proxy",
    )
    # Event type toggles
    notify_fill: bool = Field(default=True, description="Notify on !fill requests")
    notify_rejection: bool = Field(default=True, description="Notify on rejections")
    notify_signing: bool = Field(default=True, description="Notify on transaction signing")
    notify_mempool: bool = Field(default=True, description="Notify on mempool detection")
    notify_confirmed: bool = Field(default=True, description="Notify on confirmation")
    notify_nick_change: bool = Field(default=True, description="Notify on nick change")
    notify_disconnect: bool = Field(default=True, description="Notify on directory disconnect")
    notify_coinjoin_start: bool = Field(default=True, description="Notify on CoinJoin start")
    notify_coinjoin_complete: bool = Field(default=True, description="Notify on CoinJoin complete")
    notify_coinjoin_failed: bool = Field(default=True, description="Notify on CoinJoin failure")
    notify_peer_events: bool = Field(default=False, description="Notify on peer connect/disconnect")
    notify_rate_limit: bool = Field(default=True, description="Notify on rate limit bans")
    notify_startup: bool = Field(default=True, description="Notify on component startup")

Notification system configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var component_name : str

The type of the None singleton.

var enabled : bool

The type of the None singleton.

var include_amounts : bool

The type of the None singleton.

var include_nick : bool

The type of the None singleton.

var include_txids : bool

The type of the None singleton.

var model_config

The type of the None singleton.

var notify_coinjoin_complete : bool

The type of the None singleton.

var notify_coinjoin_failed : bool

The type of the None singleton.

var notify_coinjoin_start : bool

The type of the None singleton.

var notify_confirmed : bool

The type of the None singleton.

var notify_disconnect : bool

The type of the None singleton.

var notify_fill : bool

The type of the None singleton.

var notify_mempool : bool

The type of the None singleton.

var notify_nick_change : bool

The type of the None singleton.

var notify_peer_events : bool

The type of the None singleton.

var notify_rate_limit : bool

The type of the None singleton.

var notify_rejection : bool

The type of the None singleton.

var notify_signing : bool

The type of the None singleton.

var notify_startup : bool

The type of the None singleton.

var title_prefix : str

The type of the None singleton.

var urls : list[str]

The type of the None singleton.

var use_tor : bool

The type of the None singleton.

class OrderbookWatcherSettings (**data: Any)
Expand source code
class OrderbookWatcherSettings(BaseModel):
    """Orderbook watcher specific settings."""

    http_host: str = Field(
        default="0.0.0.0",
        description="HTTP server bind address",
    )
    http_port: int = Field(
        default=8000,
        ge=1,
        le=65535,
        description="HTTP server port",
    )
    update_interval: int = Field(
        default=60,
        ge=10,
        description="Update interval in seconds",
    )
    mempool_api_url: str = Field(
        default="http://mempopwcaqoi7z5xj5zplfdwk5bgzyl3hemx725d4a3agado6xtk3kqd.onion/api",
        description="Mempool API URL for transaction lookups",
    )
    mempool_web_url: str | None = Field(
        default="https://mempool.sgn.space",
        description="Mempool web URL for human-readable links",
    )
    uptime_grace_period: int = Field(
        default=60,
        ge=0,
        description="Grace period before tracking uptime",
    )
    max_message_size: int = Field(
        default=2097152,
        ge=1024,
        description="Maximum message size in bytes (2MB default)",
    )
    connection_timeout: float = Field(
        default=30.0,
        gt=0.0,
        description="Connection timeout in seconds",
    )

Orderbook watcher specific settings.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var connection_timeout : float

The type of the None singleton.

var http_host : str

The type of the None singleton.

var http_port : int

The type of the None singleton.

var max_message_size : int

The type of the None singleton.

var mempool_api_url : str

The type of the None singleton.

var mempool_web_url : str | None

The type of the None singleton.

var model_config

The type of the None singleton.

var update_interval : int

The type of the None singleton.

var uptime_grace_period : int

The type of the None singleton.

class TakerSettings (**data: Any)
Expand source code
class TakerSettings(BaseModel):
    """Taker-specific settings."""

    counterparty_count: int = Field(
        default=10,
        ge=1,
        le=20,
        description="Number of makers to select for CoinJoin",
    )
    max_cj_fee_abs: int = Field(
        default=500,
        ge=0,
        description="Maximum absolute CoinJoin fee in satoshis",
    )
    max_cj_fee_rel: str = Field(
        default="0.001",
        description="Maximum relative CoinJoin fee (0.001 = 0.1%)",
    )
    tx_fee_factor: float = Field(
        default=3.0,
        ge=1.0,
        description="Multiply estimated fee by this factor",
    )
    fee_block_target: int | None = Field(
        default=None,
        ge=1,
        le=1008,
        description="Target blocks for fee estimation",
    )
    bondless_makers_allowance: float = Field(
        default=0.0,
        ge=0.0,
        le=1.0,
        description="Fraction of time to choose makers randomly",
    )
    bond_value_exponent: float = Field(
        default=1.3,
        gt=0.0,
        description="Exponent for fidelity bond value calculation",
    )
    bondless_require_zero_fee: bool = Field(
        default=True,
        description="Require zero absolute fee for bondless maker spots",
    )
    maker_timeout_sec: int = Field(
        default=60,
        ge=10,
        description="Timeout for maker responses",
    )
    order_wait_time: float = Field(
        default=120.0,
        ge=1.0,
        description=(
            "Seconds to wait for orderbook responses. Empirical testing shows 95th "
            "percentile response time over Tor is ~101s. Default 120s (with 20% buffer) "
            "captures ~95% of offers."
        ),
    )
    tx_broadcast: str = Field(
        default="random-peer",
        description="Broadcast policy: self, random-peer, multiple-peers, not-self",
    )
    broadcast_peer_count: int = Field(
        default=3,
        ge=1,
        description="Number of peers for multiple-peers broadcast",
    )
    minimum_makers: int = Field(
        default=1,
        ge=1,
        description="Minimum number of makers required",
    )
    rescan_interval_sec: int = Field(
        default=600,
        ge=60,
        description="Interval for periodic wallet rescans",
    )

Taker-specific settings.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var bond_value_exponent : float

The type of the None singleton.

var bondless_makers_allowance : float

The type of the None singleton.

var bondless_require_zero_fee : bool

The type of the None singleton.

var broadcast_peer_count : int

The type of the None singleton.

var counterparty_count : int

The type of the None singleton.

var fee_block_target : int | None

The type of the None singleton.

var maker_timeout_sec : int

The type of the None singleton.

var max_cj_fee_abs : int

The type of the None singleton.

var max_cj_fee_rel : str

The type of the None singleton.

var minimum_makers : int

The type of the None singleton.

var model_config

The type of the None singleton.

var order_wait_time : float

The type of the None singleton.

var rescan_interval_sec : int

The type of the None singleton.

var tx_broadcast : str

The type of the None singleton.

var tx_fee_factor : float

The type of the None singleton.

class TorSettings (**data: Any)
Expand source code
class TorSettings(BaseModel):
    """Tor proxy and control port configuration."""

    # SOCKS proxy settings
    socks_host: str = Field(
        default="127.0.0.1",
        description="Tor SOCKS5 proxy host",
    )
    socks_port: int = Field(
        default=9050,
        ge=1,
        le=65535,
        description="Tor SOCKS5 proxy port",
    )

    # Control port settings
    control_enabled: bool = Field(
        default=True,
        description="Enable Tor control port integration for ephemeral hidden services",
    )
    control_host: str = Field(
        default="127.0.0.1",
        description="Tor control port host",
    )
    control_port: int = Field(
        default=9051,
        ge=1,
        le=65535,
        description="Tor control port",
    )
    cookie_path: str | None = Field(
        default=None,
        description="Path to Tor cookie auth file",
    )
    password: SecretStr | None = Field(
        default=None,
        description="Tor control port password (use cookie auth instead if possible)",
    )

    # Hidden service target (for makers)
    target_host: str = Field(
        default="127.0.0.1",
        description="Target host for Tor hidden service (usually container name in Docker)",
    )

Tor proxy and control port configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var control_enabled : bool

The type of the None singleton.

var control_host : str

The type of the None singleton.

var control_port : int

The type of the None singleton.

var cookie_path : str | None

The type of the None singleton.

var model_config

The type of the None singleton.

var password : pydantic.types.SecretStr | None

The type of the None singleton.

var socks_host : str

The type of the None singleton.

var socks_port : int

The type of the None singleton.

var target_host : str

The type of the None singleton.

class WalletSettings (**data: Any)
Expand source code
class WalletSettings(BaseModel):
    """Wallet configuration."""

    mixdepth_count: int = Field(
        default=5,
        ge=1,
        le=10,
        description="Number of mixdepths (privacy compartments)",
    )
    gap_limit: int = Field(
        default=20,
        ge=6,
        description="BIP44 gap limit for address scanning",
    )
    dust_threshold: int = Field(
        default=27300,
        ge=0,
        description="Dust threshold in satoshis",
    )
    smart_scan: bool = Field(
        default=True,
        description="Use smart scan for fast startup",
    )
    background_full_rescan: bool = Field(
        default=True,
        description="Run full blockchain rescan in background",
    )
    scan_lookback_blocks: int = Field(
        default=52560,
        ge=0,
        description="Blocks to look back for smart scan (~1 year default)",
    )
    scan_start_height: int | None = Field(
        default=None,
        ge=0,
        description="Explicit start height for initial scan (overrides scan_lookback_blocks if set)",
    )
    default_fee_block_target: int = Field(
        default=3,
        ge=1,
        le=1008,
        description="Default block target for fee estimation in wallet transactions",
    )
    mnemonic_file: str | None = Field(
        default=None,
        description="Default path to mnemonic file",
    )
    mnemonic_password: SecretStr | None = Field(
        default=None,
        description="Password for encrypted mnemonic file",
    )
    bip39_passphrase: SecretStr | None = Field(
        default=None,
        description="BIP39 passphrase (13th/25th word). For security, prefer BIP39_PASSPHRASE env var.",
    )

Wallet configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var background_full_rescan : bool

The type of the None singleton.

var bip39_passphrase : pydantic.types.SecretStr | None

The type of the None singleton.

var default_fee_block_target : int

The type of the None singleton.

var dust_threshold : int

The type of the None singleton.

var gap_limit : int

The type of the None singleton.

var mixdepth_count : int

The type of the None singleton.

var mnemonic_file : str | None

The type of the None singleton.

var mnemonic_password : pydantic.types.SecretStr | None

The type of the None singleton.

var model_config

The type of the None singleton.

var scan_lookback_blocks : int

The type of the None singleton.

var scan_start_height : int | None

The type of the None singleton.

var smart_scan : bool

The type of the None singleton.