From 4926384a655d0defdca9948babfaf218dc3bc1b1 Mon Sep 17 00:00:00 2001 From: Armin Sabouri Date: Mon, 4 Aug 2025 08:36:59 -0400 Subject: [PATCH] Add amount to recv session context Currently application developers are having to apply the amount to the bip21 uri outside of the API. https://github.com/payjoin/rust-payjoin/blob/5ecfed745bd33f6d5706f497bdb7662a3a7c9177/payjoin-cli/src/app/v2/mod.rs#L112-L113 This is a convenience that allows amount to be already applied to the bip21. It also allows us to capture this information in the session history. --- payjoin-cli/src/app/v2/mod.rs | 4 ++-- .../dart/test/test_payjoin_integration_test.dart | 2 +- .../dart/test/test_payjoin_unit_test.dart | 2 ++ .../python/test/test_payjoin_integration_test.py | 2 +- .../python/test/test_payjoin_unit_test.py | 2 ++ payjoin-ffi/src/receive/mod.rs | 2 ++ payjoin/src/core/receive/v2/mod.rs | 12 ++++++++++-- payjoin/src/core/send/v2/mod.rs | 2 +- payjoin/tests/integration.rs | 16 ++++++++++++---- 9 files changed, 33 insertions(+), 11 deletions(-) diff --git a/payjoin-cli/src/app/v2/mod.rs b/payjoin-cli/src/app/v2/mod.rs index 2213c7e76..15a209598 100644 --- a/payjoin-cli/src/app/v2/mod.rs +++ b/payjoin-cli/src/app/v2/mod.rs @@ -106,11 +106,11 @@ impl AppTrait for App { self.config.v2()?.pj_directory.clone(), ohttp_keys, None, + Some(amount), ) .save(&persister)?; println!("Receive session established"); - let mut pj_uri = session.pj_uri(); - pj_uri.amount = Some(amount); + let pj_uri = session.pj_uri(); println!("Request Payjoin by sharing this Payjoin Uri:"); println!("{}", pj_uri); diff --git a/payjoin-ffi/dart/test/test_payjoin_integration_test.dart b/payjoin-ffi/dart/test/test_payjoin_integration_test.dart index 2d9d4ff80..79fcb9b4b 100644 --- a/payjoin-ffi/dart/test/test_payjoin_integration_test.dart +++ b/payjoin-ffi/dart/test/test_payjoin_integration_test.dart @@ -135,7 +135,7 @@ payjoin.Initialized create_receiver_context( payjoin.OhttpKeys ohttp_keys, InMemoryReceiverPersister persister) { var receiver = payjoin.UninitializedReceiver() - .createSession(address, directory, ohttp_keys, null) + .createSession(address, directory, ohttp_keys, null, null) .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 ed3fa5670..c6fe0dad2 100644 --- a/payjoin-ffi/dart/test/test_payjoin_unit_test.dart +++ b/payjoin-ffi/dart/test/test_payjoin_unit_test.dart @@ -109,6 +109,7 @@ void main() { "https://example.com", payjoin.OhttpKeys.fromString( "OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), + null, null) .save(persister); final result = payjoin.replayReceiverEventLog(persister); @@ -126,6 +127,7 @@ void main() { "https://example.com", payjoin.OhttpKeys.fromString( "OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), + null, null) .save(receiver_persister); var uri = receiver.pjUri(); diff --git a/payjoin-ffi/python/test/test_payjoin_integration_test.py b/payjoin-ffi/python/test/test_payjoin_integration_test.py index fc9a31861..36cec8b1b 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).save(recv_persister) + receiver = UninitializedReceiver().create_session(address=receiver_address, directory=directory.as_string(), ohttp_keys=ohttp_keys, expire_after=None, amount=None).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 d935f5454..63d5f06f4 100644 --- a/payjoin-ffi/python/test/test_payjoin_unit_test.py +++ b/payjoin-ffi/python/test/test_payjoin_unit_test.py @@ -54,6 +54,7 @@ def test_receiver_persistence(self): address, "https://example.com", payjoin.OhttpKeys.from_string("OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), + None, None ).save(persister) result = payjoin.payjoin_ffi.replay_receiver_event_log(persister) @@ -83,6 +84,7 @@ def test_sender_persistence(self): address, "https://example.com", payjoin.OhttpKeys.from_string("OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"), + None, None ).save(persister) uri = receiver.pj_uri() diff --git a/payjoin-ffi/src/receive/mod.rs b/payjoin-ffi/src/receive/mod.rs index 03c8bf544..13ef17509 100644 --- a/payjoin-ffi/src/receive/mod.rs +++ b/payjoin-ffi/src/receive/mod.rs @@ -236,6 +236,7 @@ impl UninitializedReceiver { directory: String, ohttp_keys: Arc, expire_after: Option, + amount: Option, ) -> InitialReceiveTransition { InitialReceiveTransition(Arc::new(RwLock::new(Some( payjoin::receive::v2::Receiver::create_session( @@ -243,6 +244,7 @@ impl UninitializedReceiver { directory, (*ohttp_keys).clone().into(), expire_after.map(Duration::from_secs), + amount.map(payjoin::bitcoin::Amount::from_sat), ), )))) } diff --git a/payjoin/src/core/receive/v2/mod.rs b/payjoin/src/core/receive/v2/mod.rs index 6367fd1dc..50546f422 100644 --- a/payjoin/src/core/receive/v2/mod.rs +++ b/payjoin/src/core/receive/v2/mod.rs @@ -29,7 +29,7 @@ use std::time::{Duration, SystemTime}; use bitcoin::hashes::{sha256, Hash}; use bitcoin::psbt::Psbt; -use bitcoin::{Address, FeeRate, OutPoint, Script, TxOut}; +use bitcoin::{Address, Amount, FeeRate, OutPoint, Script, TxOut}; pub(crate) use error::InternalSessionError; pub use error::SessionError; use serde::de::Deserializer; @@ -70,6 +70,7 @@ pub struct SessionContext { mailbox: Option, ohttp_keys: OhttpKeys, expiry: SystemTime, + amount: Option, s: HpkeKeyPair, e: Option, } @@ -270,6 +271,7 @@ impl Receiver { directory: impl IntoUrl, ohttp_keys: OhttpKeys, expire_after: Option, + amount: Option, ) -> MaybeBadInitInputsTransition, IntoUrlError> { let directory = match directory.into_url() { Ok(url) => url, @@ -284,6 +286,7 @@ impl Receiver { expiry: SystemTime::now() + expire_after.unwrap_or(TWENTY_FOUR_HOURS_DEFAULT_EXPIRY), s: HpkeKeyPair::gen_keypair(), e: None, + amount, }; MaybeBadInitInputsTransition::success( SessionEvent::Created(session_context.clone()), @@ -1025,7 +1028,10 @@ pub(crate) fn pj_uri<'a>( pj.set_ohttp(session_context.ohttp_keys.clone()); pj.set_exp(session_context.expiry); let extras = PayjoinExtras { endpoint: pj, output_substitution }; - bitcoin_uri::Uri::with_extras(session_context.address.clone(), extras) + let mut uri = bitcoin_uri::Uri::with_extras(session_context.address.clone(), extras); + uri.amount = session_context.amount; + + uri } #[cfg(test)] @@ -1056,6 +1062,7 @@ pub mod test { expiry: SystemTime::now() + Duration::from_secs(60), s: HpkeKeyPair::gen_keypair(), e: None, + amount: None, }); pub(crate) fn unchecked_proposal_v2_from_test_vector() -> UncheckedProposal { @@ -1290,6 +1297,7 @@ pub mod test { SHARED_CONTEXT.directory.clone(), SHARED_CONTEXT.ohttp_keys.clone(), None, + None, ) .save(&noop_persister) .expect("Noop persister shouldn't fail"); diff --git a/payjoin/src/core/send/v2/mod.rs b/payjoin/src/core/send/v2/mod.rs index 89ff960a0..86d0ff912 100644 --- a/payjoin/src/core/send/v2/mod.rs +++ b/payjoin/src/core/send/v2/mod.rs @@ -657,7 +657,7 @@ 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) + let pj_uri = Receiver::create_session(address.clone(), directory, ohttp_keys, None, None) .save(&NoopSessionPersister::default()) .expect("receiver should succeed") .pj_uri(); diff --git a/payjoin/tests/integration.rs b/payjoin/tests/integration.rs index 676320dc6..afec40fbb 100644 --- a/payjoin/tests/integration.rs +++ b/payjoin/tests/integration.rs @@ -210,7 +210,7 @@ mod integration { .assume_checked(); let noop_persister = NoopSessionPersister::default(); let mut bad_initializer = - Receiver::create_session(mock_address, directory, bad_ohttp_keys, None) + Receiver::create_session(mock_address, directory, bad_ohttp_keys, None, None) .save(&noop_persister)?; let (req, _ctx) = bad_initializer.create_poll_request(&ohttp_relay)?; agent @@ -254,6 +254,7 @@ mod integration { directory.clone(), ohttp_keys.clone(), Some(Duration::from_secs(0)), + None, ) .save(&recv_noop_persister)?; match expired_receiver.create_poll_request(&ohttp_relay) { @@ -310,6 +311,7 @@ mod integration { directory.clone(), ohttp_keys.clone(), None, + None, ) .save(&persister)?; println!("session: {:#?}", &session); @@ -436,6 +438,7 @@ mod integration { directory.clone(), ohttp_keys.clone(), None, + None, ) .save(&recv_persister)?; println!("session: {:#?}", &session); @@ -621,9 +624,14 @@ mod integration { let recv_persister = NoopSessionPersister::default(); let send_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) - .save(&recv_persister)?; + let mut session = Receiver::create_session( + address, + directory.clone(), + ohttp_keys.clone(), + None, + None, + ) + .save(&recv_persister)?; // ********************** // Inside the V1 Sender: