Skip to content

jmwallet.cli.identity

jmwallet.cli.identity

Explicit wallet identity registration and selection, independent of recovery.

Attributes

identity_app = SortedTyper(help='Remember public wallet identities, never passphrases.') module-attribute

Classes

Functions:

confirm_and_register(settings: JoinMarketSettings, mnemonic_file: Path, *, prompt_bip39_passphrase: bool = False, no_bip39_passphrase: bool = False, confirm_bip39_passphrase: bool = False, select: bool = False, yes: bool = False, password: str | None = None) -> None

Source code in jmwallet/src/jmwallet/cli/identity.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
def confirm_and_register(
    settings: JoinMarketSettings,
    mnemonic_file: Path,
    *,
    prompt_bip39_passphrase: bool = False,
    no_bip39_passphrase: bool = False,
    confirm_bip39_passphrase: bool = False,
    select: bool = False,
    yes: bool = False,
    password: str | None = None,
) -> None:
    if no_bip39_passphrase and prompt_bip39_passphrase:
        raise ValueError("Choose either no passphrase or an interactive passphrase")
    resolved = resolve_mnemonic(
        settings,
        mnemonic_file=mnemonic_file,
        bip39_passphrase="" if no_bip39_passphrase else None,
        prompt_bip39_passphrase=(
            prompt_bip39_passphrase or settings.wallet.bip39_passphrase_enabled
        ),
        validate_identity=False,
        password=password,
    )
    assert resolved is not None
    passphrase = resolved.bip39_passphrase
    if confirm_bip39_passphrase and passphrase:
        repeated = typer.prompt("Repeat BIP39 passphrase", hide_input=True)
        # BIP39 specifies NFKD, not trimming or case folding.
        import unicodedata

        if unicodedata.normalize("NFKD", repeated) != unicodedata.normalize("NFKD", passphrase):
            raise ValueError("BIP39 passphrases do not match; identity was not registered")
    identity = WalletIdentity(
        fingerprint=get_mnemonic_fingerprint(resolved.mnemonic, passphrase),
        bip39="required" if passphrase else "none",
    )
    typer.echo(f"Wallet fingerprint: {identity.fingerprint}")
    typer.echo(f"Network: {settings.network_config.network}")
    typer.echo(f"BIP39 passphrase: {identity.bip39}")
    typer.echo(
        "Metadata remembers this public identity and passphrase status, never the passphrase."
    )
    if not yes:
        # Preview only; register_identity decides atomically under its lock.
        selects = select or load_mnemonic_meta(mnemonic_file).get("selected_identity") is None
        typer.confirm(
            "Register this identity" + (" and select it?" if selects else "?"), abort=True
        )
    selected = register_identity(mnemonic_file, identity)
    if select and not selected:
        select_identity(mnemonic_file, identity.fingerprint)
        selected = True
    typer.echo(
        "Identity registered"
        + (" and selected." if selected else ". The existing selection is unchanged.")
    )

list_identities(mnemonic_file: Annotated[Path | None, typer.Option('--mnemonic-file', '-f')] = None, json_output: Annotated[bool, typer.Option('--json')] = False, data_dir: Annotated[Path | None, typer.Option('--data-dir')] = None, config_file: Annotated[Path | None, typer.Option('--config-file')] = None) -> None

Source code in jmwallet/src/jmwallet/cli/identity.py
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
@identity_app.command("list")
def list_identities(
    mnemonic_file: Annotated[Path | None, typer.Option("--mnemonic-file", "-f")] = None,
    json_output: Annotated[bool, typer.Option("--json")] = False,
    data_dir: Annotated[Path | None, typer.Option("--data-dir")] = None,
    config_file: Annotated[Path | None, typer.Option("--config-file")] = None,
) -> None:
    settings = setup_cli(None, data_dir=data_dir, config_file=config_file)
    try:
        path = _path(settings, mnemonic_file)
        identities = registered_identities(path)
        # Listing also works before a default has been selected.
        from jmcore.wallet_metadata import load_mnemonic_meta

        selection = load_mnemonic_meta(path, strict=True).get("selected_identity")
        if selection is not None:
            selected_identity(path)  # Do not display an invalid default as valid.
        rows = [
            identity.model_dump() | {"selected": fp == selection}
            for fp, identity in identities.items()
        ]
        if json_output:
            typer.echo(json.dumps(rows))
        elif not rows:
            typer.echo(UPGRADE_GUIDANCE)
        else:
            for row in rows:
                typer.echo(
                    f"{row['fingerprint']}  BIP39: {row['bip39']}"
                    + ("  selected" if row["selected"] else "")
                )
    except (OSError, ValueError) as exc:
        logger.error(str(exc))
        raise typer.Exit(1) from exc

register(mnemonic_file: Annotated[Path | None, typer.Option('--mnemonic-file', '-f')] = None, prompt_bip39_passphrase: Annotated[bool, typer.Option('--prompt-bip39-passphrase')] = False, no_bip39_passphrase: Annotated[bool, typer.Option('--no-bip39-passphrase')] = False, confirm_bip39_passphrase: Annotated[bool, typer.Option('--confirm-bip39-passphrase')] = False, select: Annotated[bool, typer.Option('--select', help='Select it even when another identity is selected (first one is automatic)')] = False, yes: Annotated[bool, typer.Option('--yes', help='Confirm metadata disclosure noninteractively')] = False, data_dir: Annotated[Path | None, typer.Option('--data-dir')] = None, config_file: Annotated[Path | None, typer.Option('--config-file')] = None) -> None

Source code in jmwallet/src/jmwallet/cli/identity.py
 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
@identity_app.command("register")
def register(
    mnemonic_file: Annotated[Path | None, typer.Option("--mnemonic-file", "-f")] = None,
    prompt_bip39_passphrase: Annotated[bool, typer.Option("--prompt-bip39-passphrase")] = False,
    no_bip39_passphrase: Annotated[bool, typer.Option("--no-bip39-passphrase")] = False,
    confirm_bip39_passphrase: Annotated[bool, typer.Option("--confirm-bip39-passphrase")] = False,
    select: Annotated[
        bool,
        typer.Option(
            "--select",
            help="Select it even when another identity is selected (first one is automatic)",
        ),
    ] = False,
    yes: Annotated[
        bool, typer.Option("--yes", help="Confirm metadata disclosure noninteractively")
    ] = False,
    data_dir: Annotated[Path | None, typer.Option("--data-dir")] = None,
    config_file: Annotated[Path | None, typer.Option("--config-file")] = None,
) -> None:
    settings = setup_cli(None, data_dir=data_dir, config_file=config_file)
    try:
        confirm_and_register(
            settings,
            _path(settings, mnemonic_file),
            prompt_bip39_passphrase=prompt_bip39_passphrase,
            no_bip39_passphrase=no_bip39_passphrase,
            confirm_bip39_passphrase=confirm_bip39_passphrase,
            select=select,
            yes=yes,
        )
    except (OSError, ValueError) as exc:
        logger.error(str(exc))
        raise typer.Exit(1) from exc

select(fingerprint: str, mnemonic_file: Annotated[Path | None, typer.Option('--mnemonic-file', '-f')] = None, data_dir: Annotated[Path | None, typer.Option('--data-dir')] = None, config_file: Annotated[Path | None, typer.Option('--config-file')] = None) -> None

Source code in jmwallet/src/jmwallet/cli/identity.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@identity_app.command("select")
def select(
    fingerprint: str,
    mnemonic_file: Annotated[Path | None, typer.Option("--mnemonic-file", "-f")] = None,
    data_dir: Annotated[Path | None, typer.Option("--data-dir")] = None,
    config_file: Annotated[Path | None, typer.Option("--config-file")] = None,
) -> None:
    settings = setup_cli(None, data_dir=data_dir, config_file=config_file)
    try:
        select_identity(_path(settings, mnemonic_file), fingerprint)
    except (OSError, ValueError) as exc:
        logger.error(str(exc))
        raise typer.Exit(1) from exc
    typer.echo(f"Selected wallet identity: {fingerprint.strip().lower()}")