Repository navigation
feat(trade): standalone fixed-lot Cashu token trading (credit trading prototype) - #1107
Draft
maxie-agent wants to merge 31 commits into
Draft
maxie-agent wants to merge 31 commits into
maxie-agent wants to merge 31 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
N1: mint::validate pins the quoted lock split (total == gross, input fee == claim_fee) so a non-canonical sender split is rejected before the receiver locks its own leg, instead of leaving a live claim that always fails the pinned-fee check. N2: split the maker tick into separately budgeted refund and claim phases (50 s each). Own-mint NUT-07 failures in the refund phase are logged, never propagated, so an own-mint outage or black hole cannot skip a live claim. L1: an expired, never-submitted quote_created withdrawal is classified Refused (retryable with the same invoice), not unpaid_released. L2: a submitted, non-terminal withdrawal whose resolution errors exits 3 (unresolved work), never 1. Docs: exit 1 is never proof a submitted withdrawal was refused. Tests: tests/round5.rs r6_* (5 regressions) plus fixture faults reject_checkstate / blackhole_checkstate / die_after_melt.
…N-b tests L-a: replace the two fixed 50 s phase budgets in advance_inner with one 100 s deadline. Refund work is capped at min(now+50 s, end); the counterparty claim runs under timeout_at(end) and inherits whatever the refund did not use. The comment now names both enclosing budgets (120 s item, 60 s message path). Lab-only override scales the budgets for tests. Tests: - round7.rs r7_slow_counterparty_claim_inherits_unused_refund_budget: slow-but-live counterparty mint (per-request delay fault), fast refund, claim needs more than the refund cap but less than the deadline; SAFETY: claim journaled and lands. - N-a: r7_noncanonical_second_lock_rejected_before_taker_claims (taker validates the maker's second lock against q.give; forged non-canonical split refused, canonical lock still accepted). - N-b: both r6 own-mint outage tests now clear the fault and assert the swap reaches complete (refund postponed, not lost); P4 r7_own_mint_fully_dead_still_claims. - partial_claim_fixture/wait_past moved to tests/support (shared with round7).
…ording (N-c) crates/maxplayer-trade is its own cargo workspace and no CI job built or tested it. New job maxplayer-trade installs protoc and runs, with --locked against the crate manifest: cargo fmt --check, clippy default + lab with -D warnings (five pre-existing lint classes allowed by name), the default suite and the full lab suite (TRADE_LAB_SECONDS=1). rust-cache keyed on the crate's target. N-c: README/SKILL exit 1 = command error, pre-submission refusal, or definitive terminal unpaid_released.
This was referenced Oct 9, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Round 7 — final nits from the c5f06cf re-review (2026-10-09, head
a0058a0)The re-review of
c5f06cfwas approve-with-nits. This round closes L-a, N-a, N-b, N-c and the Info note on CI coverage. No further external review is planned.e46458badvance_innerhas one 100 s deadline. Refund work is capped atmin(now + 50 s, end). The counterparty claim runs undertimeout_at(end), so it inherits whatever the refund did not use. The comment names both enclosing budgets (120 s per recovery item; 60 s per inbound message, whereclaimingand the preimage are saved first and the next tick claims). A lab-only override scales the budgets for tests. The optional skip of the witness RPC was not taken: the partial-claim path still needs that call for the preimage.tests/round7.rsr7_slow_counterparty_claim_inherits_unused_refund_budget: slow-but-live counterparty mint (newdelay_msfault, 1.2 s per request), refund cap 4 s, deadline 30 s. The claim took ~8.4 s and still landed: claim attempt journaled, 24 sats on B, own-mint remainder refunded.e46458br7_noncanonical_second_lock_rejected_before_taker_claims:asymmetric_fees(100, 0), forged second lock of 34 sats in 11 proofs. The taker staysfirst_lockedwith no{id}-claimattempt. The honest second lock (q.give= (33, 1), unlikeq.request.funding) is then still accepted.e46458br6_own_mint_checkstate_{503,blackhole}_still_claimsnow clear the fault, advance again and assert a 112-sat own-mint balance andcomplete(reviewer probe P3). P4 is added asr7_own_mint_fully_dead_still_claims: every own-mint endpoint 503, claim lands, then the swap completes.a0058a0unpaid_released".a0058a0ci.ymljobmaxplayer-trade (fmt, clippy, default + lab tests). It installs protoc and runs,--lockedagainstcrates/maxplayer-trade/Cargo.toml:cargo fmt --check, clippy default + lab--all-targets --no-deps -- -D warnings, the default suite and the full lab suite (TRADE_LAB_SECONDS=1). rust-cache is keyed on the crate'starget; permissions arecontents: read.Correction to earlier rounds. Before this round, no job in
ci.ymlbuilt or testedcrates/maxplayer-trade, because it is its own[workspace]. The "all jobs success" CI lines in Round 6 (c5f06cf) and Round 4 below were true of the root workspace only. They were not evidence for this crate. The crate evidence was the local runs. The round-6 line "default and lab clippy … exit 0" was run without-D warnings. With-D warnings,c5f06cffails on five lint classes that were already present:collapsible_if,too_many_arguments,needless_update,cloned_ref_to_slice_refs,assertions_on_constants. The new job allows exactly those five by name. Every other lint is a hard failure. Reviewed money code was not restyled in this round.Verification at
a0058a0(fake/loopback mints only):cargo fmt --check: exit 0. Clippy default and lab with-D warningsplus the five allows: exit 0.TRADE_LAB_SECONDS=1): 136 passed, 0 failed (lib 22, cli 5, e2e 11, money 17, pinned_quote_recovery 1, relays 7, review_regressions 46, round5 26, round7 1).r5_refund_precedes_stalled_claimand the r6 503/black-hole tests still pass.CARGO_TARGET_DIR, deleted afterwards. 8/8 failed at theirSAFETY:assertion (exit 101):q.request.funding.*passed at the second call site; the check dropped.settling.a0058a0: 3/3 rounds passed (round526 +round71 per round, 81/81, 0 failures; 236–241 s per round). The mutation runner used its own target dir, so these loops ran on uncontaminated artifacts.a0058a0(run 37958874108): all 9 jobs success, including the newmaxplayer-trade (fmt, clippy, default + lab tests)(job 113916299434, 29 min). In CI that job ran default 94/0 and full lab 136/0, matching the local counts. Vercel and Vercel Preview Comments: success.Still deferred per Bob: L3, L4, L5, the earlier nits (unchanged; see Round 6), and cleanup of the five pre-existing clippy lint classes the new CI job allows by name (
collapsible_if,too_many_arguments,needless_update,cloned_ref_to_slice_refs,assertions_on_constants).This PR stays draft; passing tests are not independent approval.
Round 6 — re-review fixes (2026-10-09, head
c5f06cf)Re-review of
b3372b1findings N1, N2, L1, L2 fixed inc5f06cf9a69a94ac3f1a634fc1b7b204674b1c72. Regressions are appended totests/round5.rs(r6_*).mint::validatenow takes the quotedgross/claim_feeand requirestotal == gross && input_fee == claim_feebefore the receiver locks or reveals anything (maker checks the taker's first lock, taker checks the maker's second lock)r6_noncanonical_sender_split_rejected_before_maker_locks: taker sends 26 sats in 11 proofs on the 100-ppk mint; maker staysquoted, writes no lock attemptmaker_refundandmaker_claim, each with its own 50 s budget; own-mintrefundable/refund errors are logged, never propagated, so the claim always runs on the same tickr6_own_mint_checkstate_503_still_claims,r6_own_mint_checkstate_blackhole_still_claims: own-mint checkstate 503 / hung; maker still claims 24 sats and reachessettlingquote_createdwithdrawal classifiedrefused(retryable with the same invoice), notunpaid_releasedr6_expired_quote_created_is_refused_and_same_invoice_retries: no melt POST for the expired quote; retry with same invoice reachesdonemoney::submitted_unresolved(payment-hash lookup); CLI exits 3 when a submitted, non-terminal withdrawal errors. README/SKILL/recovery docs: exit 1 is never proof a submitted withdrawal was refusedr6_cli_mint_dropped_after_post_is_exit_three: mint dies after the melt POST; CLI exits 3Test-only fake-mint faults added:
reject_checkstate,blackhole_checkstate,die_after_melt. The N1 non-canonical lock is built by a helper inside the test file; production code has no switch.Verification at the committed source:
cargo fmt --check, default and lab clippy (--locked --all-targets --no-deps): exit 0. (Without-D warnings; see Round 7.)SAFETY:assertions: N1, N2 (503), N2 (black hole), H2 refund-before-claim order (rerun because N2 reworks that path), L1, L2. Receipts:round6/mutations.json,mutation-*.logunder workspace.openclaw/tmp.c5f06cf(run 37886904672): all 7 jobs success; Vercel and Vercel Preview Comments success. (Correction, round 7: none of these jobs built or testedcrates/maxplayer-trade.)c5f06cf: 3/3 rounds oftests/round5.rspassed, 24 tests each (72/72), zero failures (201–209 s per round).r6_cli_mint_dropped_after_post_is_exit_three(exit 1, not 3). Cause: the mutation runner built its disposable crate copy into the sharedtarget/directory, and the L2-mutantmaxplayer-tradebinary/artifacts were left there as fresh. In 20 isolated runs from a separate target dir, the test passed 20/20 (exit 3). Aftercargo clean -p maxplayer-trade, the test passed and all three loops above passed from a clean build. The full lab gate (133/0) ran before the mutations, so it was not affected. The mutation runner now uses its own target directory. Failed-attempt logs are kept under workspace.openclaw/tmp/round6/loop1-failed*.Deferred per Bob: L3, L4, L5 and the nits from the round-6 re-review are not addressed in this push.
This PR stays draft; passing tests are not independent approval.
Round-four scope closeout (evidence bb7b81e)
REQUEST-CHANGES: cashu-rust-dev reviewed
1c68714and returned H1, H2, M1–M4 and additional findings. Round 5 (b3372b1) addressed them; the round-6 re-review findings are addressed inc5f06cf(see Round 6 above). This PR stays draft; passing tests are not independent approval.Early-push disclosure: at Bob's request,
19299727e5e25479977cd95dee86a45a16bb3821was pushed before the loops, stress run and automatic refund rerun finished.serverefunded automatically (no manual maker recover); taker recovered after long+margin. Both audited refunded, 128→126 fake sats each.Redacted REPORT · Complete CLI/state handoff
Earlier body below is historical evidence; current review blockers and the scope closeout above take precedence.
Standalone Cashu HTLC trading — round 4, final-review candidate
Draft, MakePrisms-owned
feat/credit-trade; do not merge. Author-led verification,not independent reviewer approval. Only
crates/maxplayer-tradechanges. No Maxplayerjobs, production relay writes, core/CLI edits or new real funding.
Head:
9dad8e62669468492f997b4cb44fabe2b26ba8e1.Round-four comparison.
Full redacted evidence and per-mint verdicts.
Per-finding history and changes
Final verification
20/20 complete rounds passed: 860 test executions, 80 distinct suite invocations, zero
failures and no isolated reruns. Per round: 2 full trade/sell-back variants, 21 N-series/
probe/R4, 13 money and 7 relay tests. All used
--test-threads=1; first five rounds serialized,then an uninterrupted suite checkpoint and two CPU-isolated lanes. Same binaries/timings.
missing-ACK money state; withdrawal restore/change retention; default dual real-mint fence.
All restored controls passed.
Final default-binary live results
Production timings 3600/900/180/60 verified in actual quotes. Fake and real refund scenarios
ran concurrently. Both takers were killed after validating the second lock, with incoming
proofs persisted and no claim authorization; no timing override or recovery improvisation.
read on all three new defaults. Both subsequent timed refunds succeeded. Final fake
balances: maker 115 devkit/6 space; taker 6 devkit/115 space. 256 issued−14 fees=242 retained.
Maker Macadamia 116→114; taker cashu.cz 104→104; 2 real sats in fees.
witnesses with
preimageempty and one signature each. No missing/invalid/PENDING/UNSPENTproof. Secret material and signatures redacted; raw-wire and pinned-parser audits agree.
non-terminal after the new swap refunded. Independent read-only audit verified success;
the original timeout and reconciled outcome are both retained, not hidden.
Final real sat accounting and sweep
Minibits returned at 18:21 UTC. The original withdrawal
3c1baf7b-c5cd-4577-b499-7f7ca1a636efwas inspected UNPAID and resumed with its exactinvoice to
unpaid_released, zero inputs/debit. Its original journal row is preserved.Five completed sweeps returned 257 sats to the explicitly selected Minibits wallet:
29 Minibits, 2+110 Macadamia, 18+98 cashu.cz. Sweep fees: 2 mint + 8 Lightning=10 sats.
Every debit, exact change and receiver credit was checked. Both Macadamia and Nutshell
withdrawals are now live-verified, not merely lab-covered.
An initial 21-sat cashu.cz invoice was refused before inputs/POST because that mint's reserve
was 5, not 2. The queue paused; the original authorization was retained until natural expiry,
then inspected UNPAID and reconciled
unpaid_released. Only afterward were new 18/98-satplans authorized. No reserve relaxation, manual journal edit or silent replacement.
Designated wallet:9125; retained homes:22; confirmed cumulative fees:22
(10 prior +2 round-four refund +10 sweeps). 9169=9125+22+22, no unexplained sats.
The 22 retained sats are tool-constrained dust under observed reserves/positive-change
rules, not burned value. All homes and naturally expired authorizations remain preserved.
All real-home authorizations are terminal. Final read-only NUT-07 audit confirmed all
retained home proofs and all 9125 designated-wallet sats UNSPENT, zero locally held proofs.
Public accounting excludes unrelated wallet mints, which this work did not use.
Reviewer focus and known limits
Receipt-gated admission versus actual mint commits; durable retry budgets; withdrawal exact
change/reservation ordering; quarantine/settlement integration; fresh refund evidence and
missing-witness holds. No withdrawal-cancel command exists: an unaffordable unsent quote
can require waiting for expiry. Live Minibits HTLC refund, 24-hour relay retention, every
crash/power-loss boundary, pre-fix journal migration, malicious issuer behavior, key rotation,
adversarial fee changes and late server processing remain unproven. Offchain missed some
live ACKs despite later readback; the cause is unverified. Not production certification.
CI/Vercel green on this exact head (root workspace only; corrected in round 7: no job covered
crates/maxplayer-trade). All eight CI jobs passed; the sole background watcher exited 0. Vercel and Vercel Preview Comments passed. Head, MakePrisms ownership and draft state were read back together with every successful check.Round 5 — final-review fixes (2026-10-09)
Commit:
b3372b18b4016d89a31a2dea159df7aa9f8d7c88(ordinary push atop round-four evidence commitbb7b81e; evidence files unchanged). PR remains draft. All mappings below refer to this commit; tests are incrates/maxplayer-trade/tests/round5.rsunless qualified.r5_pinned_sender_fee_claims_even_with_legacy_receiver_cap,r5_probe_maker_cap_rejects_before_quote_or_lock,r5_taker_incoming_fee_cap_before_acceptancer5_refund_precedes_stalled_claim,r5_detached_serve_stalled_mint_and_info_less_refundr5_probe_reserve_refusal_terminal_and_same_invoice_retry,r5_lost_quote_and_near_expiry_are_terminal_without_inputsr5_hundred_thousand_at_two_percent_and_max_debit(100,000 sat at 2% accepted), reserve refusal test abover5_nut_error_final_and_proxy_failure_replays_identical_request, lost/near-expiry test abover5_recover_never_authorizes_quote_created(no POST, no inputs, unchanged balance asserted before return-value check)r5_probe_late_quote_never_resurrects_expired_takerr5_expired_unpaid_funding_and_paid_issuance_without_preflightr5_publication_exhaustion_not_unresolved_money_and_scan_prunedr5_refund_quarantine_keeps_known_preimage_claim_live;review_regressions::probe_taker_persistent_invalid_dleq,probe_maker_persistent_invalid_dleqr5_inbox_buffers_more_than_256_messages_across_peersr5_cli_nonfinal_withdraw_is_exit_three,r5_cli_terminal_quarantine_is_exit_fourr5_payment_hash_dedupes_case_and_mintsr5_withdraw_preflight_requires_nut05and existing preflight coveragecli::balance_and_status_work_while_writer_owns_homecli::skill_commands_and_flags_match_binary_helpNo other findings intentionally skipped. Recovery may continue an already-posted authorization; that is explicitly distinguished from an unsubmitted failed withdrawal.
Verification receipts
cargo fmt --check, default/lab clippy (--locked --all-targets --no-deps) passed.SAFETY:assertions: H1, H2 refund-before-claim order, M1, M3, M4, DLEQ-refund. Disposable mutation copies only; final source rebuilt afterward. Local receipts:round5/mutations.json,mutation-*.logunder workspace.openclaw/tmp.setsidPID=PGID=SID3769729, alive untilstate=refunded,no_manual_recover=true. Audited balances: maker A=112 plus 16 reserved for the independent second lot (initial 128), maker B=0, taker A=0. Daemon deliberately SIGKILLed only after the successful refund assertion. Receipt:round5/post-mutation-detached.log.Post-push repeat verification at
b3372b18b4016d89a31a2dea159df7aa9f8d7c88: 5/5 rounds passed, 19 tests each (95/95), zero failures; existing lab binary, no source changes or rebuilds during repeats.