Skip to content

wallet receive --token-file/stdin; document trade CLI build by manifest path - #1127

Merged
maxie-agent merged 4 commits into
mainfrom
fix/trade-build-docs-wallet-token-file
Oct 12, 2026
Merged

maxie-agent merged 4 commits into
mainfrom
fix/trade-build-docs-wallet-token-file

Conversation

@maxie-agent

@maxie-agent maxie-agent commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Two papercuts from the live trade test, plus two trade fixes from the live nostr:// test (items 3–4). Bob approved this on 2026-10-11 at 03:55 UTC.

1. Trade CLI build docs (docs only)

crates/maxplayer-trade is its own Cargo workspace, so cargo build -p maxplayer-trade from the repo root fails with exit 101: package ID specification `maxplayer-trade` did not match any packages. The README "Build and automated tests" section and the skill's references/commands.md now give the build that works:

cargo build --release --manifest-path crates/maxplayer-trade/Cargo.toml --locked
cargo install --locked --path crates/maxplayer-trade

Verified locally on this branch with PROTOC unset:

  • The release binary build exits 0.
  • cargo install --locked --path exits 0.
  • Building --examples fails in the cdk-signatory build script (Could not find protoc).
  • The same examples build exits 0 with PROTOC set.

The docs now say protoc is needed only for tests and examples. No workspace restructuring. No website or docs page mentions building the trade CLI; web/app/README.md only references its parser.

2. maxplayer wallet receive --token-file / stdin

  • maxplayer wallet receive --token-file <path>, or maxplayer wallet receive - to read the token from stdin. If you give no token at all, the command exits USAGE_ERROR with a message naming --token-file <path> and -, the same as before this PR. Stdin is read only when - is given explicitly (review r1 M1). With - on a terminal, it prints paste the token, then press Ctrl-D to stderr and then reads. The positional <token> still works for compatibility.
  • Giving a token argument (or -) together with --token-file is refused as a usage error before the home is touched.
  • Token-file and stdin reads stop at 1 MiB. Anything larger is refused without quoting its content. A --token-file that is not a regular file (FIFO, device, directory) is refused before it is opened. Input is trimmed.
  • Errors name the file or stdin, never the token. As a second guard, wallet-layer error text goes through redact_token. That removes the exact token, then any cashuA/cashuB run (any case, %-encoded too) and any hex run of 32 or more characters.
  • Help text is updated. crates/maxplayer-mint/README.md and e2e/README.md now use --token-file instead of "$(cat …token)" on argv. No other docs or skills passed the token on argv.

Scope notes (not widened): MCP has no receive tool; wallet_receive only returns a "moved to the CLI" pointer. I did not audit the daemon or other CLI paths for token-on-argv. maxplayer-trade receive already uses --token-file or stdin.

3. maxplayer-trade status now lists sends (visibility bug)

report.rs (#1125) never rendered send records (#1123), so a nostr send stuck in prepared with all 650 sats of inputs reserved printed "UNRESOLVED: none" and "0 unresolved"; only status --json showed it. Each send now gets one line: id, mint, state, amount, fee, reclaimed, reclaim fee and reclaim state (in flight / done / available until redeemed / manual recovery / …).

  • Non-terminal sends (prepared/submitted/swapped/reclaiming) go under UNRESOLVED.
  • Manual-recovery sends (inputs_spent, quarantined, reclaim_quarantined, reclaim_unresolved) are flagged MANUAL RECOVERY, listed as unresolved, and counted like quarantined swaps and receives.
  • An unreadable send row is reported as unresolved rather than skipped.

commands.md now lists send among the status record kinds.

Test readable_status_lists_unresolved_and_settled_sends_with_json_matching_counts (tests/send.rs):

  • prepared, submitted, reclaiming and inputs_spent sends appear under UNRESOLVED; sent and reclaimed appear as settled.
  • The summary line matches the counts derived from status --json: 4 unresolved (1 of them need manual recovery).
  • No token is printed.

Mutation check: deleting the send-rendering loop fails this test (exit 101).

4. recover says why an item was deferred

The receive, send, funding, withdrawal and swap recovery loops now print recovery deferred (<reason>), where the reason is either timeout after Ns or the {:#} error chain. The outer "money recovery deferred" line now uses {:#} too.

Every reason goes through redact_reason, which replaces cashuA…/cashuB… tokens and hex runs of 32+ characters (secrets, preimages, proof C, blinded messages, signatures) and caps the length at 600 characters. Short hex such as keyset ids and NUT codes stays readable.

Tests:

  • recover_prints_redacted_deferral_reason_for_receive (tests/receive.rs): a submitted receive against a dead mint runs the real recover binary, exits 3, prints a concrete reason, and stderr/stdout contain no token, proof secret or proof C.
  • Lib unit test deferral_reason_names_timeout_and_redacts_secrets.

Local gates

  • cargo test -p maxplayer --features acp,wallet --locked --bin maxplayer wallet_cli: 31 passed, 0 failed (7 new).
  • cargo check -p maxplayer --no-default-features: ok.
  • Trade cargo fmt --check: clean.
  • Trade skill_commands_and_flags_match_binary_help: 1 passed.
  • The touched receive code is rustfmt-clean. wallet_cli.rs as a whole isn't rustfmt-clean on main, so I didn't reformat the rest of the file.
  • cargo clippy -p maxplayer --all-targets --no-deps -D warnings: 56 errors, none in new code. The only wallet_cli.rs hit is the existing collapsible_if in cmd_balance. CI runs no clippy for this crate.
  • maxplayer-trade (same commands as its CI job, PROTOC set): cargo fmt --check clean; clippy -D warnings with the CI allow-list exits 0 for both default and lab; default tests and --features lab (TRADE_LAB_SECONDS=1) tests all pass with --test-threads=1, 0 failures.

Review round 1 (cashu-rust-dev, at fd954d6)

Changes are in e722658c and 405b2f71.

Finding Status What changed
M1 wallet receive with no token blocks on an open stdin pipe fixed With no token it exits USAGE_ERROR again, and the message names --token-file <path> and `-`. Stdin is read only for an explicit -. Usage line: maxplayer wallet receive (--token-file <path> | -) …. Mint README wording updated to match.
N1 explicit - on a TTY refused fixed It now prints paste the token, then press Ctrl-D on stderr, then reads.
L1 no size limit; FIFO/device paths fixed Token-file and stdin reads use take(1 MiB + 1). Over the cap gives … is larger than 1048576 bytes, without quoting content. A --token-file that is not a regular file is refused before open. Non-UTF-8 input is refused without echoing bytes.
L2 wallet-side redaction matches only the exact token fixed redact_token replaces the exact token first. It then removes cashuA/cashuB runs (any case, %-encoded too) and hex runs of 32 or more characters. This duplicates the small redact_reason scan locally, because maxplayer does not depend on maxplayer-trade and I did not want to grow core for it.
L3 prefix variants / % / base64url secrets fixed (partly skipped) Prefix matching is now case-insensitive, and a token run continues past %, so nothing after it leaks. Skipped: redacting 40+ char base64url runs. That rule would also eat npubs (63 chars), nostr://npub… mint URLs, URL path segments and other long ids that operators need to read. Non-hex NUT-00 secrets therefore stay uncovered (theoretical; no path quotes them). The mnemonic stays out of scope (no path). New unit cases: +/ alphabet, uppercase and mixed-case hex, token at end of string, glued token, %-encoding, and 600-character truncation applied after redaction.
L4 sent send with no token file skipped Not trivial. rewritten is #[serde(skip)] (it is set only for this command), so no journaled field records whether the file was written. Fixing this needs a new field.
L5 "unresolved" vs "manual" fixed (wording) The summary reads N unresolved (M of them need manual recovery). Exit codes are unchanged.
N2 --token-file - / --token-file=PATH skipped Both are already refused or handled safely.
N3 unreadable token file → USAGE_ERROR skipped Kept on purpose: the exit code is unchanged since the original PR.
N4 test placement and per-char String alloc fixed use super::*; is back at the top of mod tests. Both redactors scan bytes.

Tests

  • receive_wallet_error_never_contains_the_token now injects a wallet error through a test seam (cmd_receive_with). The error echoes the whole token, a 20-character prefix of it and a 64-hex secret. The test asserts that all three are redacted on stderr. The old real-wallet path stays covered as receive_real_wallet_error_for_a_malformed_token_omits_the_token.
  • New wallet tests:
    • receive_without_a_token_is_a_usage_error_and_never_reads_stdin: TTY and pipe; stdin panics if anyone reads it; no home is created.
    • receive_dash_on_a_terminal_hints_then_reads_and_refuses_empty_input
    • receive_token_reads_are_capped_at_one_mib_without_quoting_content: over the cap, exactly at the cap, and non-UTF-8.
    • receive_token_file_must_be_a_regular_file: a directory and /dev/null.
    • redact_secret_runs_catches_token_and_secret_shapes_and_keeps_identifiers
  • report::tests::send_line_flags_every_state: a table over all 12 SendStates giving the expected (unresolved, manual) flags. The expected values are written out in the table, not derived from terminal()/manual_recovery(), and an exhaustive match makes a new state fail to compile until it is added.
  • Trade lib: redact_reason_covers_prefix_variants_alphabets_case_and_truncation.

Mutation checks (each mutant applied alone and then reverted; every one failed its named test):

Mutant Test that failed
implicit stdin restored (([], None) => TokenSource::Stdin) receive_without_a_token…, at the stdin panic
size cap disabled receive_token_reads_are_capped…
regular-file check disabled receive_token_file_must_be_a_regular_file
raw wallet error printed (the redact_token call removed) receive_wallet_error_never_contains_the_token
exact-token-only redaction (shape pass removed) the same test
TTY hint removed receive_dash_on_a_terminal…
send_line rule !terminal() || manual → !terminal() send_line_flags_every_state
case-sensitive cashuA prefix redact_reason_covers…
% removed from the token run redact_reason_covers…

Local gates at e722658 (--test-threads=1, PROTOC set):

  • cargo test -p maxplayer --locked: 218 passed, 0 failed (11 suites).
  • maxplayer-trade default: 189 passed, 0 failed.
  • maxplayer-trade --features lab with TRADE_LAB_SECONDS=1: 255 passed, 0 failed.
  • cargo fmt --check for maxplayer-trade: clean. The touched wallet_cli.rs regions are rustfmt-clean; the rest of the file was left unformatted, as before.
  • maxplayer-trade clippy -D warnings with the CI allow-list exits 0 for both default and lab.
  • Root cargo clippy -p maxplayer --all-targets --no-deps -D warnings: 46 errors, all pre-existing and none in wallet_cli.rs. The root crate has no lab feature; the --no-default-features variant does not compile its test targets on this base either (checks/seller_node gated). CI runs no clippy for the root crate.

…est path

- maxplayer wallet receive accepts --token-file <path>, or the token on stdin
  ('-' or no argument); the positional token still works. Argument+file is
  refused, input is trimmed, errors never contain the token.
- maxplayer-mint docs use --token-file instead of "$(cat ...)" on argv.
- maxplayer-trade README/skill: cargo build -p maxplayer-trade fails from the
  root (own workspace); give the --manifest-path release build and note protoc
  is only needed for tests/examples.
@vercel

vercel Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
maxplayer Ready Ready Preview Oct 12, 2026 6:19am UTC

Request Review

…very defers

- report.rs renders every send (id, mint, amount, fee, state, reclaimed,
  reclaim fee/state); non-terminal and manual-recovery sends are UNRESOLVED
  and counted in the summary (a stuck prepared send printed 'UNRESOLVED: none').
- receive/send/money/coordinator recovery: 'recovery deferred (<reason>)' with
  'timeout after Ns' or the {:#} error chain, tokens and 32+ hex runs redacted.
- tests: status send rendering vs status --json counts; recover receive deferral
  reason is secret-free; redact_reason unit test. commands.md status wording.
…ile token reads; broader redaction

wallet receive (maxplayer):
- M1: no token -> USAGE_ERROR naming `--token-file <path>` and `-`; stdin only for explicit `-`
  (an open, never-closed stdin pipe can no longer hang the command). Usage line updated.
- N1: `-` on a terminal prints "paste the token, then press Ctrl-D" and reads.
- L1: token-file/stdin reads capped at 1 MiB (take(MAX+1)), refused without quoting content;
  non-regular --token-file (FIFO/device/dir) refused before open; non-UTF-8 refused.
- L2: wallet errors redact the exact token, then cashuA/cashuB runs (any case, %-encoded)
  and 32+ hex runs (same logic as maxplayer_trade::redact_reason, duplicated minimally).
- Test seam (cmd_receive_with) so receive_wallet_error_never_contains_the_token injects a
  token-echoing wallet error and bites when the redact call is removed.

maxplayer-trade:
- L3: case-insensitive cashuA/cashuB prefix, runs continue past `%`; byte scan (N4 alloc).
  Base64url-run redaction skipped (would eat npubs, mint URL paths and long ids).
- L5: summary reads "N unresolved (M of them need manual recovery)"; exit codes unchanged.
- send_line table test over every SendState; redact_reason unit cases (+/ alphabet,
  upper/mixed hex, token at end, %-encoding, truncation at 600). N4: tests below use super::*.
@maxie-agent
maxie-agent marked this pull request as ready for review October 12, 2026 18:01
@maxie-agent
maxie-agent merged commit 2fe4dd0 into main Oct 12, 2026
11 checks passed
maxie-agent added a commit that referenced this pull request Oct 12, 2026
# Conflicts:
#	crates/maxplayer-trade/tests/nostr.rs
maxie-agent added a commit that referenced this pull request Oct 12, 2026
# Conflicts:
#	crates/maxplayer-trade/src/dryrun.rs
#	crates/maxplayer-trade/src/money.rs
maxie-agent added a commit that referenced this pull request Oct 12, 2026
# Conflicts:
#	crates/maxplayer-trade/src/coordinator.rs
#	crates/maxplayer-trade/tests/nostr.rs

This branch was successfully deployed

1 active deployment
Preview — 405b2f71 Deployed Oct 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant