Skip to content

jmwallet.cli._wallet_selection

jmwallet.cli._wallet_selection

Helpers for resolving a wallet identity (8-char BIP32 fingerprint) for CLI commands that read unencrypted, per-wallet on-disk data such as jm-wallet history, jm-wallet list-bonds and jm-wallet registry-show.

These commands originally required the wallet mnemonic (and, when a BIP39 passphrase was set, the passphrase too) just to recompute the fingerprint that keys the data directory. That hurts usability:

  • the user already typed the passphrase elsewhere (info, send);
  • knowing the fingerprint at all should be enough to read public data;
  • when only one wallet has ever written to the directory, asking for the mnemonic again is needless friction.

This module centralizes the lookup order so every command that needs a wallet fingerprint behaves the same:

  1. --wallet-fingerprint <fp> if given (validated).
  2. Derived from --mnemonic-file (+ BIP39 passphrase resolution).
  3. Auto-detected when exactly one wallet identity is present on disk.
  4. Otherwise: a clear error listing the known fingerprints and the ways to disambiguate (--mnemonic-file [-passphrase] / --wallet-fingerprint / --all-wallets where applicable).

Classes

Functions

resolve_wallet_fingerprint(settings: JoinMarketSettings, *, mnemonic_file: Path | None, wallet_fingerprint: str | None, prompt_bip39_passphrase: bool, list_known_fingerprints: Callable[[], list[str]], command_label: str, allow_all_wallets: bool = False, fall_back_to_configured_mnemonic: bool = False) -> str | None

Resolve the active wallet fingerprint for an offline read command.

Args: settings: CLI settings (used to read configured mnemonic / passphrase). mnemonic_file: Optional --mnemonic-file value. wallet_fingerprint: Optional --wallet-fingerprint value (8 hex chars). prompt_bip39_passphrase: Whether to prompt for a BIP39 passphrase when the mnemonic is supplied and no passphrase is otherwise configured. list_known_fingerprints: Callable returning the fingerprints already present on disk for this command's data source (e.g. history.csv rows or fidelity_bonds_*.json files). Used to auto-detect a single wallet and to render a helpful error listing the alternatives. command_label: Human-readable label used in error messages, e.g. "jm-wallet history". allow_all_wallets: When True, the error message for the ambiguous case mentions --all-wallets as an alternative. fall_back_to_configured_mnemonic: When True and neither --wallet-fingerprint nor --mnemonic-file was given, derive the fingerprint from the mnemonic configured in config.toml / env / the default wallet path (the same resolution jm-wallet info uses). This makes per-wallet read commands work out of the box for the configured wallet. It is opt-in because commands that aggregate across wallets (e.g. history) must not silently filter to one wallet.

Returns: The resolved 8-char fingerprint, or raises typer.Exit(1) when the input is ambiguous or invalid. When no wallet identity is determinable at all and the data source is empty, returns None so the caller can fall through to its own "nothing to show" path.

Source code in jmwallet/src/jmwallet/cli/_wallet_selection.py
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def resolve_wallet_fingerprint(
    settings: JoinMarketSettings,
    *,
    mnemonic_file: Path | None,
    wallet_fingerprint: str | None,
    prompt_bip39_passphrase: bool,
    list_known_fingerprints: Callable[[], list[str]],
    command_label: str,
    allow_all_wallets: bool = False,
    fall_back_to_configured_mnemonic: bool = False,
) -> str | None:
    """Resolve the active wallet fingerprint for an offline read command.

    Args:
        settings: CLI settings (used to read configured mnemonic / passphrase).
        mnemonic_file: Optional ``--mnemonic-file`` value.
        wallet_fingerprint: Optional ``--wallet-fingerprint`` value (8 hex chars).
        prompt_bip39_passphrase: Whether to prompt for a BIP39 passphrase
            when the mnemonic is supplied and no passphrase is otherwise
            configured.
        list_known_fingerprints: Callable returning the fingerprints
            already present on disk for this command's data source
            (e.g. ``history.csv`` rows or ``fidelity_bonds_*.json``
            files). Used to auto-detect a single wallet and to render a
            helpful error listing the alternatives.
        command_label: Human-readable label used in error messages,
            e.g. ``"jm-wallet history"``.
        allow_all_wallets: When ``True``, the error message for the
            ambiguous case mentions ``--all-wallets`` as an alternative.
        fall_back_to_configured_mnemonic: When ``True`` and neither
            ``--wallet-fingerprint`` nor ``--mnemonic-file`` was given,
            derive the fingerprint from the mnemonic configured in
            ``config.toml`` / env / the default wallet path (the same
            resolution ``jm-wallet info`` uses). This makes per-wallet
            read commands work out of the box for the configured wallet.
            It is opt-in because commands that aggregate across wallets
            (e.g. ``history``) must not silently filter to one wallet.

    Returns:
        The resolved 8-char fingerprint, or raises ``typer.Exit(1)``
        when the input is ambiguous or invalid. When no wallet identity
        is determinable at all and the data source is empty, returns
        ``None`` so the caller can fall through to its own
        "nothing to show" path.
    """
    # 1) Explicit --wallet-fingerprint short-circuits everything else.
    if wallet_fingerprint:
        return validate_fingerprint(wallet_fingerprint)

    # 2) --mnemonic-file derives the fingerprint. Note: we deliberately
    # do NOT fall through to settings-configured mnemonics here. Auto-
    # filtering an offline read command by whichever mnemonic happens to
    # be configured in ``config.toml`` would silently hide history rows
    # written by other wallets (and legacy untagged rows). Users who
    # want the configured mnemonic to drive selection must still pass
    # ``--mnemonic-file`` explicitly.
    if mnemonic_file is not None:
        try:
            resolved = resolve_mnemonic(
                settings,
                mnemonic_file=mnemonic_file,
                prompt_bip39_passphrase=prompt_bip39_passphrase,
            )
        except (FileNotFoundError, ValueError) as e:
            logger.error(str(e))
            raise typer.Exit(1)
        if resolved is None:
            logger.error("No mnemonic available; cannot derive wallet fingerprint.")
            raise typer.Exit(1)
        from jmwallet.backends.descriptor_wallet import get_mnemonic_fingerprint

        return get_mnemonic_fingerprint(resolved.mnemonic, resolved.bip39_passphrase or "")

    # 2.5) Opt-in: derive from the configured mnemonic (config.toml / env /
    # default wallet path), mirroring `jm-wallet info`. Only enabled for
    # per-wallet read commands where selecting the configured wallet is the
    # expected behavior (not for cross-wallet aggregates like history).
    if fall_back_to_configured_mnemonic:
        try:
            configured = resolve_mnemonic(
                settings,
                prompt_bip39_passphrase=prompt_bip39_passphrase,
                required=False,
            )
        except (FileNotFoundError, ValueError) as e:
            logger.debug(f"Configured mnemonic not usable for fingerprint resolution: {e}")
            configured = None
        if configured is not None:
            from jmwallet.backends.descriptor_wallet import get_mnemonic_fingerprint

            return get_mnemonic_fingerprint(configured.mnemonic, configured.bip39_passphrase or "")

    # 3) Auto-detect when exactly one wallet has ever written here.
    known = list_known_fingerprints()
    if len(known) == 1:
        fp = known[0]
        logger.info(
            f"Using the only wallet present in the data directory (fingerprint: {fp}). "
            "Pass --wallet-fingerprint or --mnemonic-file to be explicit."
        )
        return fp

    # 4) Ambiguous or empty: error with actionable guidance.
    if len(known) > 1:
        logger.error(
            f"{command_label} found multiple wallets in this data directory; please "
            "pick one explicitly."
        )
        logger.info("Known wallet fingerprints:")
        for fp in known:
            logger.info(f"  - {fp}")
        hints = [
            "--mnemonic-file <file> [--prompt-bip39-passphrase] to derive the active wallet",
            "--wallet-fingerprint <fp> if you already know it (see 'jm-wallet info')",
        ]
        if allow_all_wallets:
            hints.append("--all-wallets to include entries from every wallet")
        logger.info("Options:")
        for hint in hints:
            logger.info(f"  - {hint}")
        raise typer.Exit(1)

    return None

validate_fingerprint(fingerprint: str) -> str

Normalize and validate an 8-char hex wallet fingerprint.

Raises typer.Exit(1) with a friendly message on invalid input. Returns the lowercased fingerprint on success.

Source code in jmwallet/src/jmwallet/cli/_wallet_selection.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def validate_fingerprint(fingerprint: str) -> str:
    """Normalize and validate an 8-char hex wallet fingerprint.

    Raises ``typer.Exit(1)`` with a friendly message on invalid input.
    Returns the lowercased fingerprint on success.
    """
    fp = fingerprint.strip().lower()
    if len(fp) != 8:
        logger.error(f"--wallet-fingerprint must be exactly 8 hex chars, got {len(fp)}: {fp!r}")
        raise typer.Exit(1)
    try:
        bytes.fromhex(fp)
    except ValueError:
        logger.error(f"--wallet-fingerprint must be valid hex, got {fp!r}")
        raise typer.Exit(1)
    return fp