Skip to content

Compute (potentially reorging) Settlement Status From Wallet Data#1692

Draft
DanGould wants to merge 5 commits into
payjoin:masterfrom
DanGould:settlement-status
Draft

Compute (potentially reorging) Settlement Status From Wallet Data#1692
DanGould wants to merge 5 commits into
payjoin:masterfrom
DanGould:settlement-status

Conversation

@DanGould

@DanGould DanGould commented Jun 29, 2026

Copy link
Copy Markdown
Member

This is following up on #1684 where I figured out Monitor's status can go stale in the case of a reorg. Instead, we can run a classifier that can always classify the status based on mempool+chain state data instead.

This classification, that tracks OutPoint status also makes the #1218 workaround that skips monitoring the status of non-segwit outputs obsolete and can actually monitor non-segwit spending Payjoin cooperative txs.

Sessions still close once a Receiver<Monitor> detects a transaction but the actual SettlementStatus is determined by calling classify on chain state each time.

Co-authored by Claude code

Pull Request Checklist

Please confirm the following before requesting review:

DanGould added 5 commits June 29, 2026 23:55
Introduce outpoint-based settlement attribution for a monitored
session. A pure, total classify maps caller-supplied chain facts to a
settlement status without any I/O or persisted events.

Add the compute types SettlementStatus and SettlementOutcome
{Cooperative, Fallback, Other} (sealed #[non_exhaustive]) plus
ChainView/OutpointSpend, and the primitive getters the wallet needs
(payjoin_txid, fallback_txid, contested_outpoints, receiver_input).

classify recognizes a cooperative settlement by either signal: the
spending transaction carries the receiver's contributed input (the
input fingerprint) or, for an output-substitution proposal, reproduces
the proposal's output set (a cut-through). Both are structural, so a
non-SegWit txid change does not hide them. Precedence is
Cooperative > Fallback > Other.
Add Receiver<Monitor>::conclude, which persists the single terminal
Closed(SessionOutcome) event once the caller's confidence bar is met,
mapping Cooperative -> Success, Fallback -> FallbackBroadcasted, and
Other -> Other.

Grow the persisted SessionOutcome for settlement attribution: collapse
Success to a unit variant (the spending transaction is chain data the
caller already holds, so witnesses are not persisted), add Other(Txid)
mapped to SessionStatus::Failed, and seal the enum #[non_exhaustive]
with an aborted() constructor so downstream crates keep building the
replay sentinel.

Re-express the legacy check_for_transaction over classify via a
txid-only ChainView, concluding a unit Success. Update the payjoin-cli
SessionOutcome matches and history construction, and the integration
assertions, for the unit Success.
Add the foreign-facing settlement surface: a ChainBackend trait that
reports each contested outpoint's spend status, the ChainSpend record,
and SettlementStatus/SettlementOutcome uniffi mirrors. Monitor::classify
queries the backend, builds a ChainView, and returns the status without
persisting anything.

SettlementOutcome is #[non_exhaustive] in core, so the conversion keeps
a wildcard arm that degrades a finer label this binding predates to
Other until the binding is regenerated.
Drive the monitor loop from classify: build a ChainView by querying the
wallet for each contested outpoint (get_raw_transaction_verbose for the
spending tx and its confirmations), then render the status each tick as
Pending / cooperative / fallback / other, concluding once a configurable
confirmation bar is met.
Spell out the Monitor contract so integrators do not trust an imprecise
0-conf verdict. The proposal and the fallback double-spend the sender's
inputs, so detection is necessary but not sufficient: only confirmation
resolves the race, and confirmation depth is a confidence dial, not a
state transition.

Document the division of labor (library knows the semantics, wallet
knows the ground truth and holds the spending transaction), the
classify/conclude flow, and that the legacy check_for_transaction
cannot surface Other and concludes optimistically on first sight.
@DanGould DanGould force-pushed the settlement-status branch from 8832e47 to e7c0dde Compare June 29, 2026 17:03
@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 28389258738

Coverage increased (+0.3%) to 85.769%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 27 uncovered changes across 2 files (461 of 488 lines covered, 94.47%).
  • 2 coverage regressions across 2 files.

Uncovered Changes

File Changed Covered %
payjoin-cli/src/app/v2/mod.rs 59 36 61.02%
payjoin/src/core/receive/v2/mod.rs 408 404 99.02%
Total (4 files) 488 461 94.47%

Coverage Regressions

2 previously-covered lines in 2 files lost coverage.

File Lines Losing Coverage Coverage
payjoin-cli/src/app/v2/mod.rs 1 51.94%
payjoin/src/core/receive/v2/mod.rs 1 92.78%

Coverage Stats

Coverage Status
Relevant Lines: 15565
Covered Lines: 13350
Line Coverage: 85.77%
Coverage Strength: 351.75 hits per line

💛 - Coveralls

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.

2 participants