Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 81 additions & 3 deletions payjoin-ffi/src/receive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ macro_rules! impl_terminal_cancel_for_receiver {
#[uniffi::export]
impl $ty {
/// Cancel the Payjoin session immediately.
///
/// Returns an [`CancelTransition`] that, once persisted, closes the
/// the receiver session.
pub fn cancel(&self) -> CancelTransition {
let transition = self.0.clone().cancel();
CancelTransition {
Expand All @@ -174,6 +177,9 @@ macro_rules! impl_pending_fallback_cancel_for_receiver {
#[uniffi::export]
impl $ty {
/// Cancel the Payjoin session and return pending fallback handling.
///
/// Returns an [`CancelTransition`] that, once persisted, yields a
/// [`ReceiverPendingFallback`].
pub fn cancel(&self) -> CancelTransition {
let transition = self.0.clone().cancel();
CancelTransition {
Expand Down Expand Up @@ -201,6 +207,10 @@ impl_pending_fallback_cancel_for_receiver!(Monitor);
#[uniffi::export]
impl HasReplyableError {
/// Cancel the Payjoin session without posting the receiver error response.
///
/// Returns an [`CancelTransition`] that, once persisted, yields either
/// a [`ReceiverPendingFallback`] if current session has validated fallback tx,
/// or otherwise closes the session.
pub fn cancel(&self) -> CancelTransition {
let transition = self.0.clone().cancel();
CancelTransition {
Expand Down Expand Up @@ -699,7 +709,11 @@ impl Initialized {
.map_err(Into::into)
}

/// The response can either be an UncheckedOriginalPayload or an ACCEPTED message indicating no UncheckedOriginalPayload is available yet.
/// Process the polling response from the directory.
///
/// Returns an [`InitializedTransition`] that, once persisted, yields either
/// an [`UncheckedOriginalPayload`] if the sender's Original PSBT is available,
/// or [`Initialized`] if no proposal has arrived yet.
pub fn process_response(&self, body: &[u8], ctx: &ClientResponse) -> InitializedTransition {
InitializedTransition(Arc::new(RwLock::new(Some(
self.0.clone().process_response(body, ctx.into()),
Expand Down Expand Up @@ -781,6 +795,11 @@ pub trait CanBroadcast: Send + Sync {

#[uniffi::export]
impl UncheckedOriginalPayload {
/// Check that the sender's Original PSBT is suitable for broadcast, ensuring
/// it can be used as a fallback if the payjoin does not complete.
///
/// Returns an [`UncheckedOriginalPayloadTransition`] that, once persisted,
/// yields a [`MaybeInputsOwned`] to continue validation.
pub fn check_broadcast_suitability(
&self,
min_fee_rate_sat_per_kwu: Option<u64>,
Expand Down Expand Up @@ -846,12 +865,21 @@ pub trait IsScriptOwned: Send + Sync {

#[uniffi::export]
impl MaybeInputsOwned {
/// The Sender’s Original PSBT
/// Extract the transaction from the Original PSBT for scheduling broadcast as a
/// fallback in case the payjoin does not complete.
///
/// Returns the consensus-encoded raw transaction bytes.
pub fn extract_tx_to_schedule_broadcast(&self) -> Vec<u8> {
payjoin::bitcoin::consensus::encode::serialize(
&self.0.clone().extract_tx_to_schedule_broadcast(),
)
}

/// Check that none of the Original PSBT's inputs belong to the receiver,
/// preventing an attacker from spending the receiver's own inputs.
///
/// Returns a [`MaybeInputsOwnedTransition`] that, once persisted,
/// yields a [`MaybeInputsSeen`] to continue validation.
pub fn check_inputs_not_owned(
&self,
is_owned: Arc<dyn IsScriptOwned>,
Expand Down Expand Up @@ -902,6 +930,12 @@ pub trait IsOutputKnown: Send + Sync {

#[uniffi::export]
impl MaybeInputsSeen {
/// Check that none of the inputs have been seen before, preventing input
/// probing and replay attacks (where inputs have been used in a previous
/// payjoin attempt).
///
/// Returns a [`MaybeInputsSeenTransition`] that, once persisted,
/// yields an [`OutputsUnknown`] to continue validation.
pub fn check_no_inputs_seen_before(
&self,
is_known: Arc<dyn IsOutputKnown>,
Expand Down Expand Up @@ -951,7 +985,11 @@ impl_save_for_transition!(OutputsUnknownTransition, WantsOutputs);

#[uniffi::export]
impl OutputsUnknown {
/// Find which outputs belong to the receiver
/// Identify which outputs in the original transaction belong to the receiver
/// and ensure at least one output pays the receiver.
///
/// Returns an [`OutputsUnknownTransition`] that, once persisted,
/// yields a [`WantsOutputs`] to continue the proposal.
pub fn identify_receiver_outputs(
&self,
is_receiver_output: Arc<dyn IsScriptOwned>,
Expand Down Expand Up @@ -994,8 +1032,14 @@ impl_save_for_transition!(WantsOutputsTransition, WantsInputs);

#[uniffi::export]
impl WantsOutputs {
/// Returns whether output substitution is enabled for this session.
pub fn output_substitution(&self) -> OutputSubstitution { self.0.output_substitution() }

/// Replace all receiver outputs with the provided `replacement_outputs`,
/// and set up the `drain_script` as the receiver-owned output whose value
/// may be adjusted based on modifications in subsequent states.
///
/// Returns an updated [`WantsOutputs`] with the replaced outputs.
pub fn replace_receiver_outputs(
&self,
replacement_outputs: Vec<TxOut>,
Expand All @@ -1013,6 +1057,9 @@ impl WantsOutputs {
.map_err(Into::into)
}

/// Substitute the receiver output script with the provided script.
///
/// Returns an updated [`WantsOutputs`] with the substituted output.
pub fn substitute_receiver_script(
&self,
output_script_pubkey: Vec<u8>,
Expand All @@ -1026,6 +1073,10 @@ impl WantsOutputs {
.map_err(Into::into)
}

/// Commit the output modifications and proceed to input contribution.
///
/// Returns a [`WantsOutputsTransition`] that, once persisted,
/// yields a [`WantsInputs`].
pub fn commit_outputs(&self) -> WantsOutputsTransition {
WantsOutputsTransition(Arc::new(RwLock::new(Some(self.0.clone().commit_outputs()))))
}
Expand Down Expand Up @@ -1082,6 +1133,9 @@ impl WantsInputs {
}
}

/// Add the provided inputs to the payjoin proposal.
///
/// Returns an updated [`WantsInputs`] with the contributed inputs.
pub fn contribute_inputs(
&self,
replacement_inputs: Vec<Arc<InputPair>>,
Expand All @@ -1095,6 +1149,10 @@ impl WantsInputs {
.map_err(Into::into)
}

/// Commit the input contributions and proceed to fee negotiation.
///
/// Returns a [`WantsInputsTransition`] that, once persisted,
/// yields a [`WantsFeeRange`].
pub fn commit_inputs(&self) -> WantsInputsTransition {
WantsInputsTransition(Arc::new(RwLock::new(Some(self.0.clone().commit_inputs()))))
}
Expand Down Expand Up @@ -1239,6 +1297,10 @@ pub trait ProcessPsbt: Send + Sync {

#[uniffi::export]
impl ProvisionalProposal {
/// Finalize the proposal by signing the PSBT via the `process_psbt` callback.
///
/// Returns a [`ProvisionalProposalTransition`] that, once persisted,
/// yields the final [`PayjoinProposal`].
pub fn finalize_proposal(
&self,
process_psbt: Arc<dyn ProcessPsbt>,
Expand All @@ -1253,6 +1315,7 @@ impl ProvisionalProposal {
))))
}

/// Extract the PSBT that needs to be signed by the receiver's wallet.
pub fn psbt_to_sign(&self) -> String { self.0.clone().psbt_to_sign().to_string() }
}

Expand Down Expand Up @@ -1297,6 +1360,7 @@ impl_save_for_transition!(PayjoinProposalTransition, Monitor);

#[uniffi::export]
impl PayjoinProposal {
/// Returns the finalized payjoin proposal PSBT.
pub fn psbt(&self) -> String {
<PayjoinProposal as Into<
payjoin::receive::v2::Receiver<payjoin::receive::v2::PayjoinProposal>,
Expand Down Expand Up @@ -1416,6 +1480,8 @@ impl HasReplyableErrorTransition {

#[uniffi::export]
impl HasReplyableError {
/// Construct an OHTTP encapsulated POST request to post the receiver
/// error response to the directory so it can be retrieved by the sender.
pub fn create_error_request(
&self,
ohttp_relay: String,
Expand All @@ -1425,6 +1491,13 @@ impl HasReplyableError {
})
}

/// Process the response from the directory after posting the receiver
/// error response.
///
/// Returns a [`HasReplyableErrorTransition`] that, once persisted,
/// completes the error reporting and either yields a
/// [`ReceiverPendingFallback`] if current session has validated fallback tx,
/// or otherwise closes the session.
pub fn process_error_response(
&self,
body: &[u8],
Expand Down Expand Up @@ -1505,6 +1578,11 @@ fn try_deserialize_tx(

#[uniffi::export]
impl Monitor {
/// Check the network for the payjoin or fallback transaction via the
/// `find_transaction` callback.
///
/// Returns a [`MonitorTransition`] that, once persisted, completes
/// the session if a transaction is found.
pub fn check_for_transaction(
&self,
find_transaction: Arc<dyn TransactionFinder>,
Expand Down
Loading
Loading