Skip to content
Merged
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
2 changes: 1 addition & 1 deletion payjoin-ffi/src/receive/uni.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::uri::error::IntoUrlError;
use crate::{ClientResponse, OhttpKeys, OutputSubstitution, Request};

#[derive(Clone, uniffi::Object, serde::Serialize, serde::Deserialize)]
pub struct SessionEvent(super::SessionEvent);
pub struct ReceiverSessionEvent(super::SessionEvent);

#[derive(Debug, uniffi::Object)]
pub struct NewReceiver(pub super::NewReceiver);
Expand Down
11 changes: 11 additions & 0 deletions payjoin-ffi/src/send/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ pub mod error;
#[cfg(feature = "uniffi")]
pub mod uni;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SessionEvent(payjoin::send::v2::SessionEvent);

impl From<SessionEvent> for payjoin::send::v2::SessionEvent {
fn from(value: SessionEvent) -> Self { value.0 }
}

impl From<payjoin::send::v2::SessionEvent> for SessionEvent {
fn from(value: payjoin::send::v2::SessionEvent) -> Self { SessionEvent(value) }
}

///Builder for sender-side payjoin parameters
///
///These parameters define how client wants to handle Payjoin.
Expand Down
24 changes: 24 additions & 0 deletions payjoin-ffi/src/send/uni.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@ pub use crate::send::{
};
use crate::{ClientResponse, ImplementationError, PjUri, Request};

#[derive(uniffi::Object, Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SenderSessionEvent(super::SessionEvent);

impl From<SenderSessionEvent> for super::SessionEvent {
fn from(value: SenderSessionEvent) -> Self { value.0 }
}

impl From<super::SessionEvent> for SenderSessionEvent {
fn from(value: super::SessionEvent) -> Self { SenderSessionEvent(value) }
}

#[uniffi::export]
impl SenderSessionEvent {
pub fn to_json(&self) -> Result<String, SerdeJsonError> {
serde_json::to_string(&self.0).map_err(Into::into)
}

#[uniffi::constructor]
pub fn from_json(json: String) -> Result<Self, SerdeJsonError> {
let event: payjoin::send::v2::SessionEvent = serde_json::from_str(&json)?;
Ok(SenderSessionEvent(event.into()))
}
}

#[derive(uniffi::Object)]
pub struct SenderBuilder(super::SenderBuilder);

Expand Down
1 change: 1 addition & 0 deletions payjoin/src/send/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub(crate) struct AdditionalFeeContribution {

/// Data required to validate the response against the original PSBT.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "v2", derive(serde::Serialize, serde::Deserialize, PartialEq, Eq))]
pub struct PsbtContext {
original_psbt: Psbt,
output_substitution: OutputSubstitution,
Expand Down
8 changes: 4 additions & 4 deletions payjoin/src/send/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use bitcoin::hashes::{sha256, Hash};
pub use error::{CreateRequestError, EncapsulationError};
use error::{InternalCreateRequestError, InternalEncapsulationError};
use ohttp::ClientResponse;
pub use persist::SenderToken;
pub use persist::{SenderToken, SessionEvent};
use serde::{Deserialize, Serialize};
use url::Url;

Expand Down Expand Up @@ -163,7 +163,7 @@ impl NewSender {

/// A payjoin V2 sender, allowing the construction of a payjoin V2 request
/// and the resulting [`V2PostContext`].
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WithReplyKey {
/// The v1 Sender.
pub(crate) v1: v1::Sender,
Expand Down Expand Up @@ -341,7 +341,7 @@ impl Sender<V2PostContext> {
///
/// This type is used to make a BIP77 GET request and process the response.
/// Call [`Sender<V2GetContext>::process_response`] on it to continue the BIP77 flow.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct V2GetContext {
/// The endpoint in the Payjoin URI
pub(crate) endpoint: Url,
Expand Down Expand Up @@ -415,7 +415,7 @@ impl Sender<V2GetContext> {
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct HpkeContext {
pub(crate) receiver: HpkePublicKey,
pub(crate) reply_pair: HpkeKeyPair,
Expand Down
67 changes: 67 additions & 0 deletions payjoin/src/send/v2/persist.rs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this file be renamed to session.rs as well to mirror receive?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do. Perhaps in a seperate PR?

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use url::Url;

use super::{Sender, WithReplyKey};
use crate::persist::Value;
use crate::send::v2::V2GetContext;

/// Opaque key type for the sender
#[derive(Debug, Clone, PartialEq, Eq)]
Expand All @@ -26,3 +27,69 @@ impl Value for Sender<WithReplyKey> {

fn key(&self) -> Self::Key { SenderToken(self.endpoint().clone()) }
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SessionEvent {
/// Sender was created with a HPKE key pair
CreatedReplyKey(WithReplyKey),
/// Sender POST'd the original PSBT, and waiting to receive a Proposal PSBT using GET context
V2GetContext(V2GetContext),
/// Sender received a Proposal PSBT
ProposalReceived(bitcoin::Psbt),
/// Invalid session
SessionInvalid(String),
}

#[cfg(test)]
mod tests {
use bitcoin::{FeeRate, ScriptBuf};
use payjoin_test_utils::PARSED_ORIGINAL_PSBT;

use super::*;
use crate::send::v2::HpkeContext;
use crate::send::{v1, PsbtContext};
use crate::{HpkeKeyPair, OutputSubstitution};

#[test]
fn test_sender_session_event_serialization_roundtrip() {
let endpoint = Url::parse("http://localhost:1234").expect("Valid URL");
let keypair = HpkeKeyPair::gen_keypair();
let sender_with_reply_key = WithReplyKey {
v1: v1::Sender {
psbt: PARSED_ORIGINAL_PSBT.clone(),
endpoint: endpoint.clone(),
output_substitution: OutputSubstitution::Enabled,
fee_contribution: None,
min_fee_rate: FeeRate::ZERO,
payee: ScriptBuf::from(vec![0x00]),
},
reply_key: keypair.0.clone(),
};

let v2_get_context = V2GetContext {
endpoint,
psbt_ctx: PsbtContext {
original_psbt: PARSED_ORIGINAL_PSBT.clone(),
output_substitution: OutputSubstitution::Enabled,
fee_contribution: None,
min_fee_rate: FeeRate::ZERO,
payee: ScriptBuf::from(vec![0x00]),
},
hpke_ctx: HpkeContext { receiver: keypair.clone().1, reply_pair: keypair },
};

let test_cases = vec![
SessionEvent::CreatedReplyKey(sender_with_reply_key.clone()),
SessionEvent::V2GetContext(v2_get_context.clone()),
SessionEvent::ProposalReceived(PARSED_ORIGINAL_PSBT.clone()),
SessionEvent::SessionInvalid("error message".to_string()),
];

for event in test_cases {
let serialized = serde_json::to_string(&event).expect("Should serialize");
let deserialized: SessionEvent =
serde_json::from_str(&serialized).expect("Should deserialize");
assert_eq!(event, deserialized);
}
}
}
Loading