Skip to content

jmwallet.utxo_selector

jmwallet.utxo_selector

Interactive UTXO selector TUI.

Provides an fzf-like interface for manually selecting UTXOs with multi-select support using Tab and Enter to confirm.

Classes

Functions

format_utxo_line(utxo: UTXOInfo, max_width: int = 80) -> str

Format a single UTXO for display.

Args: utxo: The UTXO to format max_width: Maximum line width

Returns: Formatted string showing mixdepth, amount, confirmations, outpoint, and label

Source code in jmwallet/src/jmwallet/utxo_selector.py
20
21
22
23
24
25
26
27
28
29
30
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
def format_utxo_line(utxo: UTXOInfo, max_width: int = 80) -> str:
    """Format a single UTXO for display.

    Args:
        utxo: The UTXO to format
        max_width: Maximum line width

    Returns:
        Formatted string showing mixdepth, amount, confirmations, outpoint, and label
    """
    amount_str = format_amount(utxo.value)
    conf_str = f"{utxo.confirmations:>6} conf"
    md_str = f"m{utxo.mixdepth}"

    # Fidelity bond indicator (locked vs unlocked)
    fb_indicator = ""
    if utxo.is_fidelity_bond:
        if utxo.is_locked:
            fb_indicator = " [FB-LOCKED]"
        else:
            fb_indicator = " [FB]"

    # Truncate txid for display
    outpoint = f"{utxo.txid[:8]}...:{utxo.vout}"

    # Label/note for UTXO type
    label_str = f" ({utxo.label})" if utxo.label else ""

    # Frozen indicator (placed after label for consistency with --extended view)
    frozen_indicator = " [FROZEN]" if utxo.frozen else ""

    line = (
        f"{md_str:>3} | {amount_str:>18} | {conf_str} | "
        f"{outpoint}{fb_indicator}{label_str}{frozen_indicator}"
    )

    if len(line) > max_width:
        line = line[: max_width - 3] + "..."

    return line

select_utxos_interactive(utxos: list[UTXOInfo], target_amount: int = 0) -> list[UTXOInfo]

Display an interactive UTXO selector.

Provides an fzf-like interface for selecting UTXOs: - Up/Down or j/k: Navigate - Tab/Space: Toggle selection - Enter: Confirm selection - q/Escape: Cancel - a: Select all - n: Deselect all - g/G: Go to top/bottom

Args: utxos: List of available UTXOs to choose from target_amount: Target amount in sats (0 for sweep, used for display)

Returns: List of selected UTXOs, empty if cancelled

Raises: RuntimeError: If not running in a terminal

Source code in jmwallet/src/jmwallet/utxo_selector.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def select_utxos_interactive(
    utxos: list[UTXOInfo],
    target_amount: int = 0,
) -> list[UTXOInfo]:
    """Display an interactive UTXO selector.

    Provides an fzf-like interface for selecting UTXOs:
    - Up/Down or j/k: Navigate
    - Tab/Space: Toggle selection
    - Enter: Confirm selection
    - q/Escape: Cancel
    - a: Select all
    - n: Deselect all
    - g/G: Go to top/bottom

    Args:
        utxos: List of available UTXOs to choose from
        target_amount: Target amount in sats (0 for sweep, used for display)

    Returns:
        List of selected UTXOs, empty if cancelled

    Raises:
        RuntimeError: If not running in a terminal
    """
    # Handle trivial cases without requiring a terminal
    if not utxos:
        return []

    # For multiple UTXOs, we need a terminal
    if not sys.stdin.isatty() or not sys.stdout.isatty():
        # If only one UTXO and no terminal, auto-select it (only if selectable)
        if len(utxos) == 1:
            utxo = utxos[0]
            if utxo.frozen or (utxo.is_fidelity_bond and utxo.is_locked):
                return []
            return utxos
        raise RuntimeError("Interactive UTXO selection requires a terminal")

    # Sort UTXOs by mixdepth, then by value (descending)
    sorted_utxos = sorted(utxos, key=lambda u: (u.mixdepth, -u.value))

    return curses.wrapper(_run_selector, sorted_utxos, target_amount)