Skip to content

jmcore.paths

jmcore.paths

Shared path utilities for JoinMarket data directories.

This module provides consistent path handling across all JoinMarket components (maker, taker, wallet) for data directories, commitment blacklists, and history.

Functions

get_all_nick_states(data_dir: Path | str | None = None) -> dict[str, str]

Read all component nick state files from the data directory.

Useful for discovering all running components and their nicks.

Args: data_dir: Optional data directory (defaults to get_default_data_dir())

Returns: Dict mapping component names to their nicks (e.g., {'maker': 'J5XXX', 'taker': 'J5YYY'})

Source code in jmcore/src/jmcore/paths.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def get_all_nick_states(data_dir: Path | str | None = None) -> dict[str, str]:
    """
    Read all component nick state files from the data directory.

    Useful for discovering all running components and their nicks.

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())

    Returns:
        Dict mapping component names to their nicks (e.g., {'maker': 'J5XXX', 'taker': 'J5YYY'})
    """
    if data_dir is None:
        data_dir = get_default_data_dir()
    elif isinstance(data_dir, str):
        data_dir = Path(data_dir)

    state_dir = data_dir / "state"
    if not state_dir.exists():
        return {}

    result: dict[str, str] = {}
    for path in state_dir.glob("*.nick"):
        component = path.stem  # e.g., 'maker' from 'maker.nick'
        try:
            nick = path.read_text().strip()
            if nick:
                result[component] = nick
        except OSError:
            continue

    return result

get_commitment_blacklist_path(data_dir: Path | None = None) -> Path

Get the path to the commitment blacklist file.

Args: data_dir: Optional data directory (defaults to get_default_data_dir())

Returns: Path to cmtdata/commitmentlist (compatible with reference JoinMarket)

Source code in jmcore/src/jmcore/paths.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def get_commitment_blacklist_path(data_dir: Path | None = None) -> Path:
    """
    Get the path to the commitment blacklist file.

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())

    Returns:
        Path to cmtdata/commitmentlist (compatible with reference JoinMarket)
    """
    if data_dir is None:
        data_dir = get_default_data_dir()

    # Use cmtdata/ subdirectory for commitment data (matches reference implementation)
    cmtdata_dir = data_dir / "cmtdata"
    cmtdata_dir.mkdir(parents=True, exist_ok=True)

    return cmtdata_dir / "commitmentlist"

get_default_data_dir() -> Path

Get the default JoinMarket data directory.

Returns ~/.joinmarket-ng or $JOINMARKET_DATA_DIR if set. Creates the directory if it doesn't exist.

For compatibility with reference JoinMarket in Docker, users can set JOINMARKET_DATA_DIR=/home/jm/.joinmarket-ng to share the same volume.

Source code in jmcore/src/jmcore/paths.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def get_default_data_dir() -> Path:
    """
    Get the default JoinMarket data directory.

    Returns ~/.joinmarket-ng or $JOINMARKET_DATA_DIR if set.
    Creates the directory if it doesn't exist.

    For compatibility with reference JoinMarket in Docker, users can
    set JOINMARKET_DATA_DIR=/home/jm/.joinmarket-ng to share the same volume.
    """
    env_path = os.getenv("JOINMARKET_DATA_DIR")
    data_dir = Path(env_path) if env_path else Path.home() / ".joinmarket-ng"

    data_dir.mkdir(parents=True, exist_ok=True)
    return data_dir

get_ignored_makers_path(data_dir: Path | None = None) -> Path

Get the path to the ignored makers file (for takers).

Args: data_dir: Optional data directory (defaults to get_default_data_dir())

Returns: Path to ignored_makers.txt

Source code in jmcore/src/jmcore/paths.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def get_ignored_makers_path(data_dir: Path | None = None) -> Path:
    """
    Get the path to the ignored makers file (for takers).

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())

    Returns:
        Path to ignored_makers.txt
    """
    if data_dir is None:
        data_dir = get_default_data_dir()

    return data_dir / "ignored_makers.txt"

get_nick_state_path(data_dir: Path | str | None = None, component: str = '') -> Path

Get the path to a component's nick state file.

The nick state file stores the current nick of a running component, allowing operators to easily identify the nick and enabling cross-component protection (e.g., taker excluding own maker nick from peer selection).

Args: data_dir: Optional data directory (defaults to get_default_data_dir()) component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook')

Returns: Path to state/.nick (e.g., ~/.joinmarket-ng/state/maker.nick)

Source code in jmcore/src/jmcore/paths.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def get_nick_state_path(data_dir: Path | str | None = None, component: str = "") -> Path:
    """
    Get the path to a component's nick state file.

    The nick state file stores the current nick of a running component,
    allowing operators to easily identify the nick and enabling cross-component
    protection (e.g., taker excluding own maker nick from peer selection).

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())
        component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook')

    Returns:
        Path to state/<component>.nick (e.g., ~/.joinmarket-ng/state/maker.nick)
    """
    if data_dir is None:
        data_dir = get_default_data_dir()
    elif isinstance(data_dir, str):
        data_dir = Path(data_dir)

    # Use state/ subdirectory to keep state files organized
    state_dir = data_dir / "state"
    state_dir.mkdir(parents=True, exist_ok=True)

    return state_dir / f"{component}.nick"

get_used_commitments_path(data_dir: Path | None = None) -> Path

Get the path to the used commitments file (for takers).

Args: data_dir: Optional data directory (defaults to get_default_data_dir())

Returns: Path to cmtdata/commitments.json (compatible with reference JoinMarket)

Source code in jmcore/src/jmcore/paths.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def get_used_commitments_path(data_dir: Path | None = None) -> Path:
    """
    Get the path to the used commitments file (for takers).

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())

    Returns:
        Path to cmtdata/commitments.json (compatible with reference JoinMarket)
    """
    if data_dir is None:
        data_dir = get_default_data_dir()

    # Use cmtdata/ subdirectory
    cmtdata_dir = data_dir / "cmtdata"
    cmtdata_dir.mkdir(parents=True, exist_ok=True)

    return cmtdata_dir / "commitments.json"

get_wallet_metadata_path(data_dir: Path | None = None) -> Path

Get the path to the wallet metadata file (BIP-329 JSONL format).

This file stores UTXO-level metadata such as frozen state and labels using the BIP-329 wallet labels export format (JSON Lines). Each line is a JSON object with a type, ref, and optional fields like spendable (for frozen/unfrozen state) and label.

The BIP-329 format enables interoperability with external wallets like Sparrow for coin control and labeling.

Args: data_dir: Optional data directory (defaults to get_default_data_dir())

Returns: Path to wallet_metadata.jsonl

Source code in jmcore/src/jmcore/paths.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def get_wallet_metadata_path(data_dir: Path | None = None) -> Path:
    """
    Get the path to the wallet metadata file (BIP-329 JSONL format).

    This file stores UTXO-level metadata such as frozen state and labels
    using the BIP-329 wallet labels export format (JSON Lines). Each line
    is a JSON object with a ``type``, ``ref``, and optional fields like
    ``spendable`` (for frozen/unfrozen state) and ``label``.

    The BIP-329 format enables interoperability with external wallets like
    Sparrow for coin control and labeling.

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())

    Returns:
        Path to wallet_metadata.jsonl
    """
    if data_dir is None:
        data_dir = get_default_data_dir()

    return data_dir / "wallet_metadata.jsonl"

read_nick_state(data_dir: Path | str | None, component: str) -> str | None

Read a component's nick from its state file.

Args: data_dir: Optional data directory (defaults to get_default_data_dir()) component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook')

Returns: The nick string if file exists and is readable, None otherwise

Source code in jmcore/src/jmcore/paths.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def read_nick_state(data_dir: Path | str | None, component: str) -> str | None:
    """
    Read a component's nick from its state file.

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())
        component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook')

    Returns:
        The nick string if file exists and is readable, None otherwise
    """
    if data_dir is None:
        data_dir = get_default_data_dir()
    elif isinstance(data_dir, str):
        data_dir = Path(data_dir)

    path = get_nick_state_path(data_dir, component)
    if path.exists():
        try:
            return path.read_text().strip()
        except OSError:
            return None
    return None

remove_nick_state(data_dir: Path | str | None, component: str) -> bool

Remove a component's nick state file (e.g., on shutdown).

Args: data_dir: Optional data directory (defaults to get_default_data_dir()) component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook')

Returns: True if file was removed, False if it didn't exist or removal failed

Source code in jmcore/src/jmcore/paths.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def remove_nick_state(data_dir: Path | str | None, component: str) -> bool:
    """
    Remove a component's nick state file (e.g., on shutdown).

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())
        component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook')

    Returns:
        True if file was removed, False if it didn't exist or removal failed
    """
    if data_dir is None:
        data_dir = get_default_data_dir()
    elif isinstance(data_dir, str):
        data_dir = Path(data_dir)

    path = get_nick_state_path(data_dir, component)
    if path.exists():
        try:
            path.unlink()
            return True
        except OSError:
            return False
    return False

write_nick_state(data_dir: Path | str | None, component: str, nick: str) -> Path

Write a component's nick to its state file.

Creates the state directory if it doesn't exist.

Args: data_dir: Optional data directory (defaults to get_default_data_dir()) component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook') nick: The nick to write (e.g., 'J5XXXXXXXXX')

Returns: Path to the written state file

Source code in jmcore/src/jmcore/paths.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def write_nick_state(data_dir: Path | str | None, component: str, nick: str) -> Path:
    """
    Write a component's nick to its state file.

    Creates the state directory if it doesn't exist.

    Args:
        data_dir: Optional data directory (defaults to get_default_data_dir())
        component: Component name (e.g., 'maker', 'taker', 'directory', 'orderbook')
        nick: The nick to write (e.g., 'J5XXXXXXXXX')

    Returns:
        Path to the written state file
    """
    path = get_nick_state_path(data_dir, component)
    path.write_text(nick + "\n")
    return path