Bug report: close/recover refuse a live OPEN deal because the prior terminal receipt of a recycled deterministic TokenContract address belongs to an earlier deal on the same address
Summary
A buyer cannot STOP a live OPEN deal from the buyer note when the TokenContract
address was previously used by an earlier deal (deployed -> stopped -> destroyed)
and then deterministically re-deployed for the current deal. exact_prior_stop_receipt
counts the historical StreamStopped from the previous lifecycle as the terminal
receipt of the current deal, compares its beneficiary against the current buyer note,
sees a mismatch, and bails — so neither dexdo close nor dexdo recover will submit
the buyer STOP that the open deal actually needs.
The current deal is still OPEN/streaming, its escrow and buyer bond stay locked in the
contract, and there is no CLI flag to bypass the guard.
Environment
- dexdo 0.2.1 (846f8e5, 2026-09-04) on Windows x86_64
- reproduced identically on v0.2.0; the guard code is unchanged on
main
- manifest: mainnet, contract generation 4.0.36
Repro command
dexdo close deal-0-17b2d080…-buyer --note-addr 0000…0004::d8a2c3165790b0bfb680cd58c3e36279c7013ed3e755ccb7430a5b076c5a9c9f --note-key <pool-key> --role buyer
dexdo recover --token-contract 0:17b2d080… --note-addr 0:d8a2c316… --note-key <pool-key>
hits the same guard first and fails identically.
Observed error
Error: prior terminal receipt beneficiary 0000000000000000000000000000000000000000000000000000000000000004::d2ff11b72afc66f1cbd5f280e30ee5c41d9362d9b24724a578ae0a7e25f83478 does not match local buyer note 0000000000000000000000000000000000000000000000000000000000000004::d8a2c3165790b0bfb680cd58c3e36279c7013ed3e755ccb7430a5b076c5a9c9f; refusing local reconciliation
Evidence: dexdo settlement-receipt --json 0:17b2d080…
The settlement sequence shows two lifecycles of the same TokenContract address:
| lifecycle |
events |
buyer note |
terminal? |
| 1 |
ContractDeployed (1788607699), StreamFunded, BuyerBondFunded, SellerBondFunded, StreamOpened, ProbeAccepted, StreamStopped (1788612589), ContractDestroyed (1788612589) |
d2ff11b7… |
yes, destroyed |
| 2 |
ContractDeployed (1788612615), StreamFunded, BuyerBondFunded, SellerBondFunded, StreamOpened (1788612782), ProbeAccepted (1788612979) |
d8a2c316… (the local buyer note) |
no — still OPEN/streaming |
Current getters agree: opened=true, probe_accepted=true, finalized_owed=1000000000,
tokens_final=1000000, tokens_pending=1000000, account active.
The receipt itself flags the anomaly under consistency_issues:
destroyed_event_with_active_account, event_after_contract_destroyed,
event_buyer_party_mismatch, event_getter_buyer_mismatch,
terminal_event_current_state_mismatch.
The TokenContract address is deterministic (same 0:17b2d080… reused across the two
deals with different buyer notes), so the historical ContractDestroyed from lifecycle 1
does not make lifecycle 2 terminal.
Root cause
crates/dexdo/src/cli/recover.rs -> exact_prior_stop_receipt:
- collects every
ProbeBurned/StreamStopped/StreamDisputed/DisputeResolved across all
historical events of the address,
- requires exactly one action and one stop in the whole history,
- compares that stop's
buyer (beneficiary) to the local buyer note.
The function assumes a TokenContract address hosts at most one deal lifetime. When the
deterministic address is destroyed and re-deployed for a new deal, the old StreamStopped
is treated as the current deal's terminal receipt, the beneficiary check fails, and the
reconciliation refuses — before any state/liveness check of the current lifecycle runs.
Impact
- A buyer with an OPEN, live, correctly-signed deal whose TC address was recycled cannot
STOP it. The deal's escrow + buyer bond remain locked. Advice given by close/recover
cannot be acted on.
reclaim is explicitly not the path (deal is OPEN, probe accepted).
- No flag bypasses this guard in v0.2.1.
Expected behaviour
close/recover should reconcile the terminal receipt against the current deal
lifecycle: e.g. scope terminal-event matching to events after the latest ContractDeployed,
or key the "already terminal" check to the current buyer note/current stream, so a STOP
receipt belonging to a destroyed previous deal on the same address does not poison a live
deal.
Suggested fix direction
- In
exact_prior_stop_receipt, split the settlement history at the latest
ContractDeployed/ContractDestroyed boundary and only consider STOP events of the
current lifecycle for the terminal-receipt check; or
- match the terminal receipt only if its beneficiary equals the current buyer note AND no
later ContractDeployed/ContractDestroyed supersedes it; otherwise fall through to the
normal OPEN-deal STOP path.
Bug report:
close/recoverrefuse a live OPEN deal because the prior terminal receipt of a recycled deterministic TokenContract address belongs to an earlier deal on the same addressSummary
A buyer cannot STOP a live OPEN deal from the buyer note when the TokenContract
address was previously used by an earlier deal (deployed -> stopped -> destroyed)
and then deterministically re-deployed for the current deal.
exact_prior_stop_receiptcounts the historical
StreamStoppedfrom the previous lifecycle as the terminalreceipt of the current deal, compares its beneficiary against the current buyer note,
sees a mismatch, and bails — so neither
dexdo closenordexdo recoverwill submitthe buyer STOP that the open deal actually needs.
The current deal is still OPEN/streaming, its escrow and buyer bond stay locked in the
contract, and there is no CLI flag to bypass the guard.
Environment
mainRepro command
dexdo recover --token-contract 0:17b2d080… --note-addr 0:d8a2c316… --note-key <pool-key>hits the same guard first and fails identically.
Observed error
Evidence:
dexdo settlement-receipt --json 0:17b2d080…The settlement sequence shows two lifecycles of the same TokenContract address:
d2ff11b7…d8a2c316…(the local buyer note)Current getters agree:
opened=true,probe_accepted=true,finalized_owed=1000000000,tokens_final=1000000,tokens_pending=1000000, account active.The receipt itself flags the anomaly under
consistency_issues:destroyed_event_with_active_account,event_after_contract_destroyed,event_buyer_party_mismatch,event_getter_buyer_mismatch,terminal_event_current_state_mismatch.The TokenContract address is deterministic (same
0:17b2d080…reused across the twodeals with different buyer notes), so the historical
ContractDestroyedfrom lifecycle 1does not make lifecycle 2 terminal.
Root cause
crates/dexdo/src/cli/recover.rs->exact_prior_stop_receipt:ProbeBurned/StreamStopped/StreamDisputed/DisputeResolvedacross allhistorical events of the address,
buyer(beneficiary) to the local buyer note.The function assumes a TokenContract address hosts at most one deal lifetime. When the
deterministic address is destroyed and re-deployed for a new deal, the old
StreamStoppedis treated as the current deal's terminal receipt, the beneficiary check fails, and the
reconciliation refuses — before any state/liveness check of the current lifecycle runs.
Impact
STOP it. The deal's escrow + buyer bond remain locked. Advice given by
close/recovercannot be acted on.
reclaimis explicitly not the path (deal is OPEN, probe accepted).Expected behaviour
close/recovershould reconcile the terminal receipt against the current deallifecycle: e.g. scope terminal-event matching to events after the latest
ContractDeployed,or key the "already terminal" check to the current buyer note/current stream, so a STOP
receipt belonging to a destroyed previous deal on the same address does not poison a live
deal.
Suggested fix direction
exact_prior_stop_receipt, split the settlement history at the latestContractDeployed/ContractDestroyedboundary and only consider STOP events of thecurrent lifecycle for the terminal-receipt check; or
later
ContractDeployed/ContractDestroyedsupersedes it; otherwise fall through to thenormal OPEN-deal STOP path.