Skip to content

Cannot close OPEN deal after TC address recycled — prior StreamStopped beneficiary mismatch #147

Description

@Pithung

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    trackedAccepted and assigned a DEXDO- ticket

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions