You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Should calling extract_v1 close the v2 session. We are assuming the v2 sender has abondoned their v2 session. It would make sense to return a state transition object which closes the current session and returns the v1 payload.
This question can be applied to the fallback more generally
In the case where the request was not able to be made, say because of a connectivity issue, and the session were still closed, would this be able to proceed? Should the session not be closed upon the response to that extraction being processed?
in what situation is fallback from v1 to v2 even reasonable? if the URI supports v2 then i think the only point of failure that that would help is an ohttp relay not working, but wouldn't it be better to retry with different relay sinstead of revealing metadata and payloads to the directory for a client that can be bothered to do v2?
i can't think of any scenario where a v2 supporting sender would decide to ignore a receiver's v2 capabilities, whereas such fallback seems like a very surprising privacy footgun, scan a v2 URI, but end up losing privacy
Thinking more generally about this. Payjoin sessions must terminate if either a fallback transaction is broadcast (with or without confirmations) or if a double spend is detected by either party.
The receiver must track:
Fallback txid
Payjoin txid (SegWit only)
Outpoints from both the fallback and payjoin proposal
Outpoints of the payjoin proposal to detect receiver double spends or if the payjoin tx has non segwit inpouts
Both roles must also confirm whether a valid payjoin transaction has been broadcast and, optionally, confirmed depending on application policy.
If either party detects a fallback broadcast, the session must be recorded as failed and closed.
If a double spend is observed, the session must also be closed fatally.
If a payjoin transaction confirms, the session can be closed successfully.
Confirmation depth should be an application-level preference based on reorg tolerance.
Whether this logic belongs in the API depends on its value in partially resolving #336. Without API support, applications must implement a bespoke solution to fetch outpoints and txids, which risks footguns. And inconsistencies across implemntations.
We can introduce a dedicated "monitoring" (better name tbd) typestate that exposes outpoints and transaction IDs for the application to scan. The state machine transitions into this typestate under two conditions:
The application extracts the fallback transaction
A payjoin proposal has been sent (receiver) or received (sender), i.e., the session’s current final state
Although double spends or fallback broadcasts may occur at any time, there is no obvious disadvantage to allowing the state machine to complete before performing monitoring checks.
Successful payjoin broadcast closes the session. Otherwise, fallback or double spend detection fatally closes it after logging an error event.
Introducing a new final typestate for monitoring could make sense but is only a partial solution, we'd still need a way to monitor fallbacks/double spends while the payjoin is still being processed and hasn't reached the final state. Maybe the monitoring checks could be exposed on SessionHistory or when replaying events?
The other question is how to ensure the monitoring interface is ergonomic and provides all the context necessary for the application to properly scan for transactions with low risk of footguns, etc.
Also, we'll need to be careful to differentiate between Payjoin session state vs. wallet state. For example, a fallback transaction might "fail and close" a Payjoin session, but the payee still got paid so it would be confusing to display a "failed" state to either the sender/receiver users.
we'd still need a way to monitor fallbacks/double spends
As discussed in yesterday's call, this must not be the responsibility of the payjoin state machine. It's the responsibility of the wallet. We're not coming up with a comprehensive transaction / history status in the payjoin crate. Rather our reference must compose the information from bitcoind and payjoin to create this history as any other implementation must, as you mention.
Ergonomics must allow this composition, not encapsulate wallet UTXO status.
I agree but currently the wallet wouldn't even know which outpoints to monitor for double spends, at least not trivially. Instead of delegating the responsibility entirely to implementers to know what to look for, we could provide monitoring helpers on SessionHistory e.g. doublespend_outpoints() -> Vec<Outpoint> with explanatory docstring (fallback_tx() is already exposed there).
Armin and I worked out some of the details of how this should work today.
tl;dr - a payjoin session is closed once there is nothing more to do in the protocol but wait for expiry, or confirmation of one of the possible transactions to settle the outcome. [edit: and this is in agreement with both Dan & spacebear's comments]
broadcast, confirmation monitoring, fee bumping etc is the wallet's responsibility, but the state machine should make it very simple for the wallet to check by feeding it relevant transactions when they are broadcast or confirmed, or by asking it what txids to wait for confirmation on (with caveats, see below in the detailed rationale for the recevier).
The sender's session closes (i.e. no new payjoin activity) when receiving the payjoin proposal. Everything that happens after is the wallet's responsibility, including signing and broadcasting. The payjoin state machine does not need to capture the fully signed transaction because wallets already deal with signing, waiting for confirmation, rebroadcasting etc and whatever the wallet does it should do consistently, regardless of whether it's a payjoin or non-payjoin transaction.
For a similar thing to make sense for the receiver's session, the state machine should be able to tell the application exactly what to check for: the spend status of specific outpoints in general, or the confirmation status of specific txids, depending on what its blockchain querying capabilities might be. If any non-segwit inputs are spent by the sender the txid is not known to the receiver until broadcast. Since the receiver is not able to reproduce the sender's signatures under any circumstances, it also seems appropriate to store the completed payjoin transcation (technically just the missing sender's witnesses are new primary information, so the event doesn't need to contain a full PSBT), but on the other hand arguably the wallet should already retain such transactions. However, the task of interpreting the different relevant transactions does seem in scope, namely a tx spending one of the sender's coins is a double spend, the fallback (whose txid we already know, so that's trivial to distinguish), or a successful payjoin. A tx spending one of the receiver's coins from the proposal PSBT is definitely the successful payjoin even if we couldn't predict it's txid ahead of time. Indicating what will happen depending on which tx gets confirmed allows the status to be reported to the user (payment failed, payment succeeded but payjoin failed (fallback confirmed), or payjoin succeeded), or make CPFP fee bumping possible.
Activity
DanGould commentedon Jul 11, 2025
In the case where the request was not able to be made, say because of a connectivity issue, and the session were still closed, would this be able to proceed? Should the session not be closed upon the response to that extraction being processed?
nothingmuch commentedon Jul 11, 2025
in what situation is fallback from v1 to v2 even reasonable? if the URI supports v2 then i think the only point of failure that that would help is an ohttp relay not working, but wouldn't it be better to retry with different relay sinstead of revealing metadata and payloads to the directory for a client that can be bothered to do v2?
i can't think of any scenario where a v2 supporting sender would decide to ignore a receiver's v2 capabilities, whereas such fallback seems like a very surprising privacy footgun, scan a v2 URI, but end up losing privacy
arminsabouri commentedon Sep 9, 2025
Thinking more generally about this. Payjoin sessions must terminate if either a fallback transaction is broadcast (with or without confirmations) or if a double spend is detected by either party.
The receiver must track:
The sender must track:
Both roles must also confirm whether a valid payjoin transaction has been broadcast and, optionally, confirmed depending on application policy.
Whether this logic belongs in the API depends on its value in partially resolving #336. Without API support, applications must implement a bespoke solution to fetch outpoints and txids, which risks footguns. And inconsistencies across implemntations.
We can introduce a dedicated "monitoring" (better name tbd) typestate that exposes outpoints and transaction IDs for the application to scan. The state machine transitions into this typestate under two conditions:
Although double spends or fallback broadcasts may occur at any time, there is no obvious disadvantage to allowing the state machine to complete before performing monitoring checks.
Successful payjoin broadcast closes the session. Otherwise, fallback or double spend detection fatally closes it after logging an error event.
spacebear21 commentedon Sep 9, 2025
Introducing a new final typestate for monitoring could make sense but is only a partial solution, we'd still need a way to monitor fallbacks/double spends while the payjoin is still being processed and hasn't reached the final state. Maybe the monitoring checks could be exposed on
SessionHistoryor when replaying events?The other question is how to ensure the monitoring interface is ergonomic and provides all the context necessary for the application to properly scan for transactions with low risk of footguns, etc.
Also, we'll need to be careful to differentiate between Payjoin session state vs. wallet state. For example, a fallback transaction might "fail and close" a Payjoin session, but the payee still got paid so it would be confusing to display a "failed" state to either the sender/receiver users.
DanGould commentedon Sep 9, 2025
As discussed in yesterday's call, this must not be the responsibility of the payjoin state machine. It's the responsibility of the wallet. We're not coming up with a comprehensive transaction / history status in the payjoin crate. Rather our reference must compose the information from bitcoind and
payjointo create this history as any other implementation must, as you mention.Ergonomics must allow this composition, not encapsulate wallet UTXO status.
spacebear21 commentedon Sep 9, 2025
I agree but currently the wallet wouldn't even know which outpoints to monitor for double spends, at least not trivially. Instead of delegating the responsibility entirely to implementers to know what to look for, we could provide monitoring helpers on
SessionHistorye.g.doublespend_outpoints() -> Vec<Outpoint>with explanatory docstring (fallback_tx()is already exposed there).nothingmuch commentedon Sep 9, 2025
Armin and I worked out some of the details of how this should work today.
tl;dr - a payjoin session is closed once there is nothing more to do in the protocol but wait for expiry, or confirmation of one of the possible transactions to settle the outcome. [edit: and this is in agreement with both Dan & spacebear's comments]
broadcast, confirmation monitoring, fee bumping etc is the wallet's responsibility, but the state machine should make it very simple for the wallet to check by feeding it relevant transactions when they are broadcast or confirmed, or by asking it what txids to wait for confirmation on (with caveats, see below in the detailed rationale for the recevier).
The sender's session closes (i.e. no new payjoin activity) when receiving the payjoin proposal. Everything that happens after is the wallet's responsibility, including signing and broadcasting. The payjoin state machine does not need to capture the fully signed transaction because wallets already deal with signing, waiting for confirmation, rebroadcasting etc and whatever the wallet does it should do consistently, regardless of whether it's a payjoin or non-payjoin transaction.
For a similar thing to make sense for the receiver's session, the state machine should be able to tell the application exactly what to check for: the spend status of specific outpoints in general, or the confirmation status of specific txids, depending on what its blockchain querying capabilities might be. If any non-segwit inputs are spent by the sender the txid is not known to the receiver until broadcast. Since the receiver is not able to reproduce the sender's signatures under any circumstances, it also seems appropriate to store the completed payjoin transcation (technically just the missing sender's witnesses are new primary information, so the event doesn't need to contain a full PSBT), but on the other hand arguably the wallet should already retain such transactions. However, the task of interpreting the different relevant transactions does seem in scope, namely a tx spending one of the sender's coins is a double spend, the fallback (whose txid we already know, so that's trivial to distinguish), or a successful payjoin. A tx spending one of the receiver's coins from the proposal PSBT is definitely the successful payjoin even if we couldn't predict it's txid ahead of time. Indicating what will happen depending on which tx gets confirmed allows the status to be reported to the user (payment failed, payment succeeded but payjoin failed (fallback confirmed), or payjoin succeeded), or make CPFP fee bumping possible.
[-]Should extracting v1 sender request close the v2 session[/-][+]Monitoring Receiver Typestate[/+]arminsabouri commentedon Oct 3, 2025
Closed in #1061