From abfc6d322b4f052b0c62d1625fde64ebeeda8247 Mon Sep 17 00:00:00 2001 From: Armin Sabouri Date: Tue, 26 Aug 2025 14:17:56 -0400 Subject: [PATCH] Add receiver builder The first typestate has always been an odd case. Stricly speaking you cannot resume from the uninitialized state. The earliest state you can resume from is `Initialized`. So this commit removes UnInitialized as a "state" and replaces it with a builder. Additionally a builder model allows the application to access the short id after the builder is created. The application may use session id as the key for the perister which is needed when you build(). --- payjoin-cli/src/app/v2/mod.rs | 19 +++-- .../test/test_payjoin_integration_test.dart | 4 +- .../dart/test/test_payjoin_unit_test.dart | 24 +++--- .../test/test_payjoin_integration_test.py | 2 +- .../python/test/test_payjoin_unit_test.py | 12 +-- payjoin-ffi/src/receive/mod.rs | 64 ++++++++------- payjoin/src/core/receive/v2/mod.rs | 77 +++++++++++++------ payjoin/src/core/receive/v2/session.rs | 7 +- payjoin/src/core/send/v2/mod.rs | 5 +- payjoin/tests/integration.rs | 41 ++++------ 10 files changed, 140 insertions(+), 115 deletions(-) diff --git a/payjoin-cli/src/app/v2/mod.rs b/payjoin-cli/src/app/v2/mod.rs index 4337bf84b..d51266958 100644 --- a/payjoin-cli/src/app/v2/mod.rs +++ b/payjoin-cli/src/app/v2/mod.rs @@ -7,7 +7,8 @@ use payjoin::persist::OptionalTransitionOutcome; use payjoin::receive::v2::{ process_err_res, replay_event_log as replay_receiver_event_log, Initialized, MaybeInputsOwned, MaybeInputsSeen, OutputsUnknown, PayjoinProposal, ProvisionalProposal, ReceiveSession, - Receiver, SessionHistory, UncheckedProposal, WantsFeeRange, WantsInputs, WantsOutputs, + Receiver, ReceiverBuilder, SessionHistory, UncheckedProposal, WantsFeeRange, WantsInputs, + WantsOutputs, }; use payjoin::send::v2::{ replay_event_log as replay_sender_event_log, SendSession, Sender, SenderBuilder, V2GetContext, @@ -158,14 +159,12 @@ impl AppTrait for App { .await? .ohttp_keys; let persister = ReceiverPersister::new(self.db.clone())?; - let session = Receiver::create_session( - address, - self.config.v2()?.pj_directory.clone(), - ohttp_keys, - None, - Some(amount), - )? - .save(&persister)?; + let session = + ReceiverBuilder::new(address, self.config.v2()?.pj_directory.clone(), ohttp_keys)? + .with_amount(amount) + .build() + .save(&persister)?; + println!("Receive session established"); let pj_uri = session.pj_uri(); println!("Request Payjoin by sharing this Payjoin Uri:"); @@ -352,7 +351,7 @@ impl App { self.finalize_proposal(proposal, persister).await, ReceiveSession::PayjoinProposal(proposal) => self.send_payjoin_proposal(proposal, persister).await, - ReceiveSession::Uninitialized(_) => + ReceiveSession::Uninitialized => return Err(anyhow!("Uninitialized receiver session")), ReceiveSession::TerminalFailure => return Err(anyhow!("Terminal receiver session")), diff --git a/payjoin-ffi/dart/test/test_payjoin_integration_test.dart b/payjoin-ffi/dart/test/test_payjoin_integration_test.dart index 79fcb9b4b..ad86d24c7 100644 --- a/payjoin-ffi/dart/test/test_payjoin_integration_test.dart +++ b/payjoin-ffi/dart/test/test_payjoin_integration_test.dart @@ -134,8 +134,8 @@ payjoin.Initialized create_receiver_context( String directory, payjoin.OhttpKeys ohttp_keys, InMemoryReceiverPersister persister) { - var receiver = payjoin.UninitializedReceiver() - .createSession(address, directory, ohttp_keys, null, null) + var receiver = payjoin.ReceiverBuilder(address, directory, ohttp_keys) + .build() .save(persister); return receiver; } diff --git a/payjoin-ffi/dart/test/test_payjoin_unit_test.dart b/payjoin-ffi/dart/test/test_payjoin_unit_test.dart index c6fe0dad2..bbfc0a600 100644 --- a/payjoin-ffi/dart/test/test_payjoin_unit_test.dart +++ b/payjoin-ffi/dart/test/test_payjoin_unit_test.dart @@ -103,15 +103,12 @@ void main() { var persister = InMemoryReceiverPersister("1"); var address = bitcoin.Address( "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4", bitcoin.Network.signet); - payjoin.UninitializedReceiver() - .createSession( - address, - "https://example.com", - payjoin.OhttpKeys.fromString( - "OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), - null, - null) - .save(persister); + payjoin.ReceiverBuilder( + address, + "https://example.com", + payjoin.OhttpKeys.fromString( + "OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), + ).build().save(persister); final result = payjoin.replayReceiverEventLog(persister); expect(result, isA(), reason: "persistence should return a replay result"); @@ -121,15 +118,14 @@ void main() { var receiver_persister = InMemoryReceiverPersister("1"); var address = bitcoin.Address( "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK", bitcoin.Network.testnet); - var receiver = payjoin.UninitializedReceiver() - .createSession( + var receiver = payjoin.ReceiverBuilder( address, "https://example.com", payjoin.OhttpKeys.fromString( "OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), - null, - null) - .save(receiver_persister); + ) + .build() + .save(receiver_persister); var uri = receiver.pjUri(); var sender_persister = InMemorySenderPersister("1"); diff --git a/payjoin-ffi/python/test/test_payjoin_integration_test.py b/payjoin-ffi/python/test/test_payjoin_integration_test.py index 36cec8b1b..c5bfb4128 100644 --- a/payjoin-ffi/python/test/test_payjoin_integration_test.py +++ b/payjoin-ffi/python/test/test_payjoin_integration_test.py @@ -84,7 +84,7 @@ async def process_receiver_proposal(self, receiver: ReceiveSession, recv_persist raise Exception(f"Unknown receiver state: {receiver}") def create_receiver_context(self, receiver_address: bitcoinffi.Address, directory: Url, ohttp_keys: OhttpKeys, recv_persister: InMemoryReceiverSessionEventLog) -> Initialized: - receiver = UninitializedReceiver().create_session(address=receiver_address, directory=directory.as_string(), ohttp_keys=ohttp_keys, expire_after=None, amount=None).save(recv_persister) + receiver = ReceiverBuilder(address=receiver_address, directory=directory.as_string(), ohttp_keys=ohttp_keys).build().save(recv_persister) return receiver async def retrieve_receiver_proposal(self, receiver: Initialized, recv_persister: InMemoryReceiverSessionEventLog, ohttp_relay: Url): diff --git a/payjoin-ffi/python/test/test_payjoin_unit_test.py b/payjoin-ffi/python/test/test_payjoin_unit_test.py index 63d5f06f4..4f6248073 100644 --- a/payjoin-ffi/python/test/test_payjoin_unit_test.py +++ b/payjoin-ffi/python/test/test_payjoin_unit_test.py @@ -50,13 +50,11 @@ class TestReceiverPersistence(unittest.TestCase): def test_receiver_persistence(self): persister = InMemoryReceiverPersister(1) address = payjoin.bitcoin.Address("tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4", payjoin.bitcoin.Network.SIGNET) - payjoin.payjoin_ffi.UninitializedReceiver().create_session( + payjoin.payjoin_ffi.ReceiverBuilder( address, "https://example.com", payjoin.OhttpKeys.from_string("OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), - None, - None - ).save(persister) + ).build().save(persister) result = payjoin.payjoin_ffi.replay_receiver_event_log(persister) self.assertTrue(result.state().is_INITIALIZED()) @@ -80,13 +78,11 @@ def test_sender_persistence(self): # Create a receiver to just get the pj uri persister = InMemoryReceiverPersister(1) address = payjoin.bitcoin.Address("2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK", payjoin.bitcoin.Network.TESTNET) - receiver = payjoin.payjoin_ffi.UninitializedReceiver().create_session( + receiver = payjoin.payjoin_ffi.ReceiverBuilder( address, "https://example.com", payjoin.OhttpKeys.from_string("OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), - None, - None - ).save(persister) + ).build().save(persister) uri = receiver.pj_uri() persister = InMemorySenderPersister(1) diff --git a/payjoin-ffi/src/receive/mod.rs b/payjoin-ffi/src/receive/mod.rs index 48a31540a..5c2b0563d 100644 --- a/payjoin-ffi/src/receive/mod.rs +++ b/payjoin-ffi/src/receive/mod.rs @@ -7,7 +7,7 @@ pub use error::{ ReplyableError, SelectionError, SessionError, }; use payjoin::bitcoin::psbt::Psbt; -use payjoin::bitcoin::FeeRate; +use payjoin::bitcoin::{Amount, FeeRate}; use payjoin::persist::{MaybeFatalTransition, NextStateTransition}; use crate::bitcoin_ffi::{Address, OutPoint, Script, TxOut}; @@ -93,7 +93,7 @@ impl From for ReceiveSession { fn from(value: payjoin::receive::v2::ReceiveSession) -> Self { use payjoin::receive::v2::ReceiveSession; match value { - ReceiveSession::Uninitialized(_) => Self::Uninitialized, + ReceiveSession::Uninitialized => Self::Uninitialized, ReceiveSession::Initialized(inner) => Self::Initialized { inner: Arc::new(inner.into()) }, ReceiveSession::UncheckedProposal(inner) => @@ -236,47 +236,55 @@ impl InitialReceiveTransition { } } -#[derive(uniffi::Object)] -pub struct UninitializedReceiver {} +#[derive(Clone, Debug, uniffi::Object)] +pub struct ReceiverBuilder(payjoin::receive::v2::ReceiverBuilder); #[uniffi::export] -impl UninitializedReceiver { - #[allow(clippy::new_without_default)] - #[uniffi::constructor] - // TODO: no need for this constructor. `create_session` is the only way to create a receiver. - pub fn new() -> Self { Self {} } - +impl ReceiverBuilder { /// Creates a new [`Initialized`] with the provided parameters. /// /// # Parameters /// - `address`: The Bitcoin address for the payjoin session. /// - `directory`: The URL of the store-and-forward payjoin directory. /// - `ohttp_keys`: The OHTTP keys used for encrypting and decrypting HTTP requests and responses. - /// - `expire_after`: The duration after which the session expires. - /// - /// # Returns - /// A new instance of [`Initialized`]. /// /// # References /// - [BIP 77: Payjoin Version 2: Serverless Payjoin](https://github.com/bitcoin/bips/blob/master/bip-0077.md) - pub fn create_session( - &self, + #[uniffi::constructor] + pub fn new( address: Arc
, directory: String, ohttp_keys: Arc, - expire_after: Option, - amount: Option, - ) -> Result { - payjoin::receive::v2::Receiver::create_session( - Arc::unwrap_or_clone(address).into(), - directory, - Arc::unwrap_or_clone(ohttp_keys).into(), - expire_after.map(Duration::from_secs), - amount.map(payjoin::bitcoin::Amount::from_sat), - ) - .map(|receiver| InitialReceiveTransition(Arc::new(RwLock::new(Some(receiver))))) - .map_err(IntoUrlError::from) + ) -> Result { + Ok(Self( + payjoin::receive::v2::ReceiverBuilder::new( + Arc::unwrap_or_clone(address).into(), + directory, + Arc::unwrap_or_clone(ohttp_keys).into(), + ) + .map_err(IntoUrlError::from)?, + )) } + + pub fn with_amount(&self, amount_sats: u64) -> Self { + Self(self.0.clone().with_amount(Amount::from_sat(amount_sats))) + } + + pub fn with_expiry(&self, expiry: u64) -> Self { + Self(self.0.clone().with_expiry(Duration::from_secs(expiry))) + } + + pub fn build(&self) -> InitialReceiveTransition { + InitialReceiveTransition(Arc::new(RwLock::new(Some(self.0.clone().build())))) + } +} + +impl From for ReceiverBuilder { + fn from(value: payjoin::receive::v2::ReceiverBuilder) -> Self { Self(value) } +} + +impl From for payjoin::receive::v2::ReceiverBuilder { + fn from(value: ReceiverBuilder) -> Self { value.0 } } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, uniffi::Object)] diff --git a/payjoin/src/core/receive/v2/mod.rs b/payjoin/src/core/receive/v2/mod.rs index dbed3c20f..57837e813 100644 --- a/payjoin/src/core/receive/v2/mod.rs +++ b/payjoin/src/core/receive/v2/mod.rs @@ -115,7 +115,7 @@ fn short_id_from_pubkey(pubkey: &HpkePublicKey) -> ShortId { /// and the state to be updated with the next event over a uniform interface. #[derive(Debug, Clone, PartialEq)] pub enum ReceiveSession { - Uninitialized(Receiver), + Uninitialized, Initialized(Receiver), UncheckedProposal(Receiver), MaybeInputsOwned(Receiver), @@ -132,7 +132,7 @@ pub enum ReceiveSession { impl ReceiveSession { fn process_event(self, event: SessionEvent) -> Result { match (self, event) { - (ReceiveSession::Uninitialized(_), SessionEvent::Created(context)) => + (ReceiveSession::Uninitialized, SessionEvent::Created(context)) => Ok(ReceiveSession::Initialized(Receiver { state: Initialized { context } })), ( @@ -243,14 +243,11 @@ pub fn process_err_res(body: &[u8], context: ohttp::ClientResponse) -> Result<() process_post_res(body, context).map_err(|e| InternalSessionError::DirectoryResponse(e).into()) } -#[derive(Debug, Clone, PartialEq)] -/// The receiver is not initialized yet, no session context is available yet -pub struct UninitializedReceiver {} - -impl State for UninitializedReceiver {} +#[derive(Debug, Clone)] +pub struct ReceiverBuilder(SessionContext); -impl Receiver { - /// Creates a new [`Receiver`] with the provided parameters. +impl ReceiverBuilder { + /// Creates a new [`ReceiverBuilder`] with the provided parameters. /// /// This is the beginning of the receiver protocol in Payjoin v2. It uses the passed address, /// store-and-forward Payjoin directory URL, and the OHTTP keys to encrypt and decrypt HTTP @@ -261,28 +258,42 @@ impl Receiver { /// /// See [BIP 77: Payjoin Version 2: Serverless Payjoin](https://github.com/bitcoin/bips/blob/master/bip-0077.md) /// for more information on the purpose of each parameter for secure Payjoin v2 functionality. - pub fn create_session( + pub fn new( address: Address, directory: impl IntoUrl, ohttp_keys: OhttpKeys, - expire_after: Option, - amount: Option, - ) -> Result>, IntoUrlError> { + ) -> Result { let directory = directory.into_url()?; let session_context = SessionContext { address, directory, - mailbox: None, ohttp_keys, - expiry: SystemTime::now() + expire_after.unwrap_or(TWENTY_FOUR_HOURS_DEFAULT_EXPIRY), s: HpkeKeyPair::gen_keypair(), + expiry: SystemTime::now() + TWENTY_FOUR_HOURS_DEFAULT_EXPIRY, + amount: None, + mailbox: None, e: None, - amount, }; - Ok(NextStateTransition::success( - SessionEvent::Created(session_context.clone()), - Receiver { state: Initialized { context: session_context } }, - )) + Ok(Self(session_context)) + } + + pub fn with_expiry(self, expiry: Duration) -> Self { + Self(SessionContext { expiry: SystemTime::now() + expiry, ..self.0 }) + } + + pub fn with_amount(self, amount: Amount) -> Self { + Self(SessionContext { amount: Some(amount), ..self.0 }) + } + + pub fn with_mailbox(self, mailbox: impl IntoUrl) -> Result { + Ok(Self(SessionContext { mailbox: Some(mailbox.into_url()?), ..self.0 })) + } + + pub fn build(self) -> NextStateTransition> { + NextStateTransition::success( + SessionEvent::Created(self.0.clone()), + Receiver { state: Initialized { context: self.0 } }, + ) } } @@ -1346,14 +1357,13 @@ pub mod test { let now = SystemTime::now(); let noop_persister = NoopSessionPersister::default(); - let session = Receiver::create_session( + let session = ReceiverBuilder::new( SHARED_CONTEXT.address.clone(), SHARED_CONTEXT.directory.clone(), SHARED_CONTEXT.ohttp_keys.clone(), - None, - None, ) .expect("constructor on test vector should not fail") + .build() .save(&noop_persister) .expect("Noop persister shouldn't fail"); let session_expiry = session.context.expiry.duration_since(now).unwrap().as_secs(); @@ -1364,6 +1374,27 @@ pub mod test { } } + #[test] + fn build_receiver_with_non_default_expiry() { + let now = SystemTime::now(); + let expiry = Duration::from_secs(60); + let noop_persister = NoopSessionPersister::default(); + let receiver = ReceiverBuilder::new( + SHARED_CONTEXT.address.clone(), + SHARED_CONTEXT.directory.clone(), + SHARED_CONTEXT.ohttp_keys.clone(), + ) + .expect("constructor on test vector should not fail") + .with_expiry(expiry) + .build() + .save(&noop_persister) + .expect("Noop persister shouldn't fail"); + assert_eq!( + receiver.context.expiry.duration_since(now).unwrap().as_secs(), + expiry.as_secs() + ); + } + #[test] fn test_v2_pj_uri() { let uri = Receiver { state: Initialized { context: SHARED_CONTEXT.clone() } }.pj_uri(); diff --git a/payjoin/src/core/receive/v2/session.rs b/payjoin/src/core/receive/v2/session.rs index 5f3b7c44d..d6d093127 100644 --- a/payjoin/src/core/receive/v2/session.rs +++ b/payjoin/src/core/receive/v2/session.rs @@ -2,7 +2,7 @@ use std::time::SystemTime; use serde::{Deserialize, Serialize}; -use super::{ReceiveSession, Receiver, SessionContext, UninitializedReceiver}; +use super::{ReceiveSession, SessionContext}; use crate::output_substitution::OutputSubstitution; use crate::persist::SessionPersister; use crate::receive::v2::{extract_err_req, SessionError}; @@ -52,7 +52,7 @@ where let logs = persister .load() .map_err(|e| InternalReplayError::PersistenceFailure(ImplementationError::new(e)))?; - let mut receiver = ReceiveSession::Uninitialized(Receiver { state: UninitializedReceiver {} }); + let mut receiver = ReceiveSession::Uninitialized; let mut history = SessionHistory::default(); for event in logs { @@ -182,7 +182,8 @@ mod tests { use crate::receive::tests::original_from_test_vector; use crate::receive::v2::test::SHARED_CONTEXT; use crate::receive::v2::{ - Initialized, MaybeInputsOwned, PayjoinProposal, ProvisionalProposal, UncheckedProposal, + Initialized, MaybeInputsOwned, PayjoinProposal, ProvisionalProposal, Receiver, + UncheckedProposal, }; fn unchecked_receiver_from_test_vector() -> Receiver { diff --git a/payjoin/src/core/send/v2/mod.rs b/payjoin/src/core/send/v2/mod.rs index b3c2a71ba..74df8fc68 100644 --- a/payjoin/src/core/send/v2/mod.rs +++ b/payjoin/src/core/send/v2/mod.rs @@ -511,7 +511,7 @@ mod test { use super::*; use crate::persist::NoopSessionPersister; - use crate::receive::v2::Receiver; + use crate::receive::v2::ReceiverBuilder; use crate::OhttpKeys; const SERIALIZED_BODY_V2: &str = "63484e696450384241484d43414141414159386e757447674a647959475857694245623435486f65396c5747626b78682f36624e694f4a6443447544414141414141442b2f2f2f2f41747956754155414141414146366b554865684a38476e536442554f4f7636756a584c72576d734a5244434867495165414141414141415871525233514a62627a30686e513849765130667074476e2b766f746e656f66544141414141414542494b6762317755414141414146366b55336b34656b47484b57524e6241317256357452356b455644564e4348415163584667415578347046636c4e56676f31575741644e3153594e583874706854414243477343527a424541694238512b41366465702b527a393276687932366c5430416a5a6e3450524c6938426639716f422f434d6b30774967502f526a3250575a3367456a556b546c6844524e415130675877544f3774396e2b563134705a366f6c6a554249514d566d7341616f4e5748564d5330324c6654536530653338384c4e697450613155515a794f6968592b464667414241425941464562324769753663344b4f35595730706677336c4770396a4d55554141413d0a763d32"; @@ -601,8 +601,9 @@ mod test { let ohttp_keys = OhttpKeys( ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).expect("valid key config"), ); - let pj_uri = Receiver::create_session(address.clone(), directory, ohttp_keys, None, None) + let pj_uri = ReceiverBuilder::new(address.clone(), directory, ohttp_keys) .expect("constructor on test vector should not fail") + .build() .save(&NoopSessionPersister::default()) .expect("receiver should succeed") .pj_uri(); diff --git a/payjoin/tests/integration.rs b/payjoin/tests/integration.rs index ecc36fd50..3094e077f 100644 --- a/payjoin/tests/integration.rs +++ b/payjoin/tests/integration.rs @@ -173,7 +173,7 @@ mod integration { use payjoin::persist::NoopSessionPersister; use payjoin::receive::v2::{ replay_event_log as replay_receiver_event_log, PayjoinProposal, Receiver, - UncheckedProposal, + ReceiverBuilder, UncheckedProposal, }; use payjoin::send::v2::SenderBuilder; use payjoin::{OhttpKeys, PjUri, UriExt}; @@ -210,7 +210,8 @@ mod integration { .assume_checked(); let noop_persister = NoopSessionPersister::default(); let mut bad_initializer = - Receiver::create_session(mock_address, directory, bad_ohttp_keys, None, None)? + ReceiverBuilder::new(mock_address, directory, bad_ohttp_keys)? + .build() .save(&noop_persister)?; let (req, _ctx) = bad_initializer.create_poll_request(&ohttp_relay)?; agent @@ -249,14 +250,10 @@ mod integration { // Inside the Receiver: let address = receiver.get_new_address(None, None)?.assume_checked(); // test session with expiry in the past - let mut expired_receiver = Receiver::create_session( - address, - directory, - ohttp_keys, - Some(Duration::from_secs(0)), - None, - )? - .save(&recv_noop_persister)?; + let mut expired_receiver = ReceiverBuilder::new(address, directory, ohttp_keys)? + .with_expiry(Duration::from_secs(0)) + .build() + .save(&recv_noop_persister)?; match expired_receiver.create_poll_request(&ohttp_relay) { // Internal error types are private, so check against a string Err(err) => assert!(err.to_string().contains("expired")), @@ -306,9 +303,9 @@ mod integration { // Inside the Receiver: let address = receiver.get_new_address(None, None)?.assume_checked(); - let mut session = - Receiver::create_session(address, directory, ohttp_keys, None, None)? - .save(&persister)?; + let mut session = ReceiverBuilder::new(address, directory, ohttp_keys)? + .build() + .save(&persister)?; println!("session: {:#?}", &session); // Poll receive request let ohttp_relay = services.ohttp_relay_url(); @@ -424,9 +421,9 @@ mod integration { let address = receiver.get_new_address(None, None)?.assume_checked(); // test session with expiry in the future - let mut session = - Receiver::create_session(address, directory, ohttp_keys, None, None)? - .save(&recv_persister)?; + let mut session = ReceiverBuilder::new(address, directory, ohttp_keys)? + .build() + .save(&recv_persister)?; println!("session: {:#?}", &session); // Poll receive request let ohttp_relay = services.ohttp_relay_url(); @@ -604,14 +601,10 @@ mod integration { let ohttp_keys = services.fetch_ohttp_keys().await?; let recv_persister = NoopSessionPersister::default(); let address = receiver.get_new_address(None, None)?.assume_checked(); - let mut session = Receiver::create_session( - address, - directory.clone(), - ohttp_keys.clone(), - None, - None, - )? - .save(&recv_persister)?; + let mut session = + ReceiverBuilder::new(address, directory.clone(), ohttp_keys.clone())? + .build() + .save(&recv_persister)?; // ********************** // Inside the V1 Sender: