From 3b0c6e548d5214521a033c80531557ee45e0301e Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 12 Mar 2025 11:03:51 -0500 Subject: [PATCH 01/22] WIP: Add WASM bindings Experimenting with exposing just the Uri type with WASM bindings first Want to test "wasm" feature flag for WASM specific modifications to methods the .cargo/config settings are mac specific Hitting issue with the proxy --- .cargo/config.toml | 3 +++ Cargo.lock | 27 +++++++++++++++++++++++++++ Cargo.toml | 15 ++++++++++++++- README.md | 6 ++++++ src/lib.rs | 9 +++++++++ src/uri.rs | 15 ++++++++++++++- 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..bea647e --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +AR = "/opt/homebrew/opt/llvm/bin/llvm-ar" +CC = "/opt/homebrew/opt/llvm/bin/clang" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 338da3c..d9339cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,6 +812,16 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1266,6 +1276,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "goblin" version = "0.8.2" @@ -2049,6 +2071,9 @@ dependencies = [ "bitcoin-ohttp", "bitcoincore-rpc", "bitcoind", + "console_error_panic_hook", + "getrandom", + "gloo-timers", "hex", "http", "ohttp-relay", @@ -2056,6 +2081,7 @@ dependencies = [ "payjoin-directory", "rcgen", "reqwest", + "ring 0.17.8", "rustls 0.22.4", "serde_json", "testcontainers", @@ -2064,6 +2090,7 @@ dependencies = [ "tokio", "uniffi", "url", + "wasm-bindgen", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8a62b21..884a9e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,17 @@ exclude = ["tests"] [features] _danger-local-https = ["payjoin/_danger-local-https"] uniffi = ["uniffi/cli", "bitcoin-ffi/default"] +wasm = [ + "wasm-bindgen", + "console_error_panic_hook", + "getrandom", + "ring", + "gloo-timers", +] [lib] name = "payjoin_ffi" -crate-type = ["lib", "staticlib", "cdylib"] +crate-type = ["lib", "staticlib", "cdylib", "rlib"] [[bin]] name = "uniffi-bindgen" @@ -30,6 +37,12 @@ serde_json = "1.0.128" thiserror = "1.0.58" uniffi = { version = "0.28.0", optional = true } url = "2.5.0" +wasm-bindgen = { version = "0.2.91", optional = true } +console_error_panic_hook = { version = "0.1.7", optional = true } +# Compatibility to compile to WASM +getrandom = { version = "0.2.15", optional = true, features = ["js"] } +ring = { version = "0.17.8", optional = true, features = ["wasm32_unknown_unknown_js"] } +gloo-timers = { version = "0.3.0", optional = true, features = ["futures"] } [dev-dependencies] bdk = { version = "0.29.0", features = ["all-keys", "use-esplora-ureq", "keys-bip39", "rpc"] } diff --git a/README.md b/README.md index 077874d..61e1d34 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,9 @@ cargo test --package payjoin_ffi --test bdk_integration_test v2_to_v2_full_cycl This project is in active development and currently in its Alpha stage. **Please proceed with caution**, particularly when using real funds. We encourage thorough review, testing, and contributions to help improve its stability and security before considering production use. + +## WASM + +```shell +wasm-pack build --features wasm +``` diff --git a/src/lib.rs b/src/lib.rs index ea81a00..debfb68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,3 +20,12 @@ pub use crate::send::uni::*; pub use crate::uri::{PjUri, Uri, Url}; #[cfg(feature = "uniffi")] uniffi::setup_scaffolding!(); + +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; +// Initialize WASM logging (optional but helpful for debugging) +#[cfg(feature = "wasm")] +#[wasm_bindgen(start)] +pub fn start() { + console_error_panic_hook::set_once(); +} \ No newline at end of file diff --git a/src/uri.rs b/src/uri.rs index 2f95a69..33efdd8 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -1,12 +1,15 @@ use std::str::FromStr; #[cfg(feature = "uniffi")] use std::sync::Arc; +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; use payjoin::bitcoin::address::NetworkChecked; use payjoin::UriExt; use crate::error::PayjoinError; #[derive(Clone)] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub struct Uri(payjoin::Uri<'static, NetworkChecked>); impl From for payjoin::Uri<'static, NetworkChecked> { fn from(value: Uri) -> Self { @@ -20,6 +23,7 @@ impl From> for Uri { } } +#[cfg_attr(feature = "wasm", wasm_bindgen)] impl Uri { pub fn parse(uri: String) -> Result { match payjoin::Uri::from_str(uri.as_str()) { @@ -40,7 +44,7 @@ impl Uri { pub fn message(&self) -> Option { self.0.message.clone().and_then(|x| String::try_from(x).ok()) } - #[cfg(not(feature = "uniffi"))] + #[cfg(not(any(feature = "uniffi", feature = "wasm")))] pub fn check_pj_supported(&self) -> Result { match self.0.clone().check_pj_supported() { Ok(e) => Ok(e.into()), @@ -62,6 +66,15 @@ impl Uri { } } } + #[cfg(feature = "wasm")] + pub fn check_pj_supported(&self) -> wasm_bindgen::JsResult { + match self.0.clone().check_pj_supported() { + Ok(e) => Ok(e.into()), + Err(_) => { + Err(wasm_bindgen::JsError::new("Uri doesn't support payjoin")) + } + } + } pub fn as_string(&self) -> String { self.0.clone().to_string() } From fc36ae595d626f48e6f191802c09ca37969e46a1 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 12 Mar 2025 11:53:34 -0500 Subject: [PATCH 02/22] WIP: Got WASM bindings to build Need to sort out how to handle errors --- Cargo.toml | 2 ++ README.md | 2 +- src/error.rs | 1 + src/lib.rs | 3 +++ src/uri.rs | 26 +++++++++++++++++++++++--- src/utils/mod.rs | 1 + src/utils/result.rs | 3 +++ 7 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 src/utils/mod.rs create mode 100644 src/utils/result.rs diff --git a/Cargo.toml b/Cargo.toml index 884a9e3..d7f1a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ wasm = [ "ring", "gloo-timers", ] +default = ["io"] +io = ["payjoin/io"] [lib] name = "payjoin_ffi" diff --git a/README.md b/README.md index 61e1d34..953e5cb 100644 --- a/README.md +++ b/README.md @@ -57,5 +57,5 @@ We encourage thorough review, testing, and contributions to help improve its sta ## WASM ```shell -wasm-pack build --features wasm +wasm-pack build --no-default-features --features wasm ``` diff --git a/src/error.rs b/src/error.rs index 264fea6..c6795fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -152,6 +152,7 @@ impl From for PayjoinError { } } +#[cfg(not(feature = "wasm"))] impl From for PayjoinError { fn from(value: payjoin::io::Error) -> Self { PayjoinError::IoError { message: value.to_string() } diff --git a/src/lib.rs b/src/lib.rs index debfb68..b90100d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,13 +2,16 @@ pub mod bitcoin_ffi; pub mod error; +#[cfg(not(feature = "wasm"))] pub mod io; pub mod ohttp; pub mod receive; pub mod request; pub mod send; pub mod uri; +mod utils; +pub use utils::*; pub use crate::bitcoin_ffi::*; pub use crate::error::PayjoinError; pub use crate::ohttp::*; diff --git a/src/uri.rs b/src/uri.rs index 33efdd8..4dd44e2 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use std::sync::Arc; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; +use crate::utils::result::JsResult; use payjoin::bitcoin::address::NetworkChecked; use payjoin::UriExt; @@ -25,12 +26,22 @@ impl From> for Uri { #[cfg_attr(feature = "wasm", wasm_bindgen)] impl Uri { + #[cfg(not(feature = "wasm"))] pub fn parse(uri: String) -> Result { match payjoin::Uri::from_str(uri.as_str()) { Ok(e) => Ok(e.assume_checked().into()), Err(e) => Err(PayjoinError::PjParseError { message: e.to_string() }), } } + + #[cfg(feature = "wasm")] + pub fn parse(uri: String) -> JsResult { + match payjoin::Uri::from_str(uri.as_str()) { + Ok(e) => Ok(e.assume_checked().into()), + Err(e) => Err(wasm_bindgen::JsError::new(&e.to_string())), + } + } + pub fn address(&self) -> String { self.clone().0.address.to_string() } @@ -67,7 +78,7 @@ impl Uri { } } #[cfg(feature = "wasm")] - pub fn check_pj_supported(&self) -> wasm_bindgen::JsResult { + pub fn check_pj_supported(&self) -> JsResult { match self.0.clone().check_pj_supported() { Ok(e) => Ok(e.into()), Err(_) => { @@ -94,22 +105,31 @@ impl<'a> From for payjoin::PjUri<'a> { #[derive(Clone)] #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] -pub struct PjUri(pub payjoin::PjUri<'static>); +#[cfg_attr(feature = "wasm", wasm_bindgen)] +pub struct PjUri( + #[wasm_bindgen(skip)] + pub payjoin::PjUri<'static> +); -#[cfg_attr(feature = "uniffi", uniffi::export)] +#[cfg_attr(feature = "wasm", wasm_bindgen)] impl PjUri { + #[wasm_bindgen(getter)] pub fn address(&self) -> String { self.0.clone().address.to_string() } + + #[wasm_bindgen(getter)] /// Number of sats requested as payment pub fn amount_sats(&self) -> Option { self.0.clone().amount.map(|e| e.to_sat()) } + #[wasm_bindgen(getter)] pub fn pj_endpoint(&self) -> String { self.0.extras.endpoint().to_string() } + #[wasm_bindgen(getter)] pub fn as_string(&self) -> String { self.0.clone().to_string() } diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..d11ca02 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod result; \ No newline at end of file diff --git a/src/utils/result.rs b/src/utils/result.rs new file mode 100644 index 0000000..40f538b --- /dev/null +++ b/src/utils/result.rs @@ -0,0 +1,3 @@ +use wasm_bindgen::JsError; + +pub type JsResult = Result; From 7765d8b186c2b3677c973bfbbef1dd6aa4184d9c Mon Sep 17 00:00:00 2001 From: River Kanies Date: Fri, 14 Mar 2025 15:28:48 -0500 Subject: [PATCH 03/22] WIP: separate crate-types for wasm to avoid name conflicts of diff crate type output files --- Cargo.toml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d7f1a48..3aaf114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ io = ["payjoin/io"] [lib] name = "payjoin_ffi" -crate-type = ["lib", "staticlib", "cdylib", "rlib"] +crate-type = ["lib", "staticlib", "cdylib"] [[bin]] name = "uniffi-bindgen" @@ -67,3 +67,13 @@ opt-level = 'z' lto = true codegen-units = 1 strip = true + +[package.metadata.wasm-pack.profile.release] +wasm-opt = false + +[package.metadata.wasm-pack.profile.dev] +wasm-opt = false + +[[target.'cfg(target_arch = "wasm32")'.lib]] +name = "payjoin_ffi_wasm" +crate-type = ["cdylib", "rlib"] From d8f90f7533645e8f57b6161a73d31effdc2a71de Mon Sep 17 00:00:00 2001 From: River Kanies Date: Mon, 17 Mar 2025 16:48:14 -0500 Subject: [PATCH 04/22] WIP: revert wasm lib name Just remove redundant (hopefully) rlib type --- Cargo.toml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3aaf114..2e89bf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,13 +67,3 @@ opt-level = 'z' lto = true codegen-units = 1 strip = true - -[package.metadata.wasm-pack.profile.release] -wasm-opt = false - -[package.metadata.wasm-pack.profile.dev] -wasm-opt = false - -[[target.'cfg(target_arch = "wasm32")'.lib]] -name = "payjoin_ffi_wasm" -crate-type = ["cdylib", "rlib"] From f958ca32f6b3501fb69118a03379a1b1bb56d491 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Tue, 18 Mar 2025 11:56:16 -0500 Subject: [PATCH 05/22] WIP: add wasm to ohttp mod --- src/ohttp.rs | 24 +++++++++++++++++++++++- src/uri.rs | 6 ++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/ohttp.rs b/src/ohttp.rs index 9f4d68a..e2ea61f 100644 --- a/src/ohttp.rs +++ b/src/ohttp.rs @@ -1,5 +1,12 @@ +#[cfg(not(feature = "wasm"))] use crate::error::PayjoinError; +#[cfg(feature = "wasm")] +use { + crate::utils::result::JsResult, + wasm_bindgen::prelude::*, +}; + impl From for OhttpKeys { fn from(value: payjoin::OhttpKeys) -> Self { Self(value) @@ -11,21 +18,36 @@ impl From for payjoin::OhttpKeys { } } #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Debug, Clone)] -pub struct OhttpKeys(pub payjoin::OhttpKeys); +pub struct OhttpKeys( + #[wasm_bindgen(skip)] + pub payjoin::OhttpKeys +); #[cfg_attr(feature = "uniffi", uniffi::export)] +#[cfg_attr(feature = "wasm", wasm_bindgen)] impl OhttpKeys { /// Decode an OHTTP KeyConfig + #[cfg(not(feature = "wasm"))] #[cfg_attr(feature = "uniffi", uniffi::constructor)] pub fn decode(bytes: Vec) -> Result { payjoin::OhttpKeys::decode(bytes.as_slice()).map(|e| e.into()).map_err(|e| e.into()) } + + #[cfg(feature = "wasm")] + #[wasm_bindgen(constructor)] + pub fn decode(bytes: Vec) -> JsResult { + payjoin::OhttpKeys::decode(bytes.as_slice()) + .map(|e| e.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } } use std::sync::Mutex; #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub struct ClientResponse(Mutex>); impl From<&ClientResponse> for ohttp::ClientResponse { diff --git a/src/uri.rs b/src/uri.rs index 4dd44e2..a7bdd1c 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -2,8 +2,10 @@ use std::str::FromStr; #[cfg(feature = "uniffi")] use std::sync::Arc; #[cfg(feature = "wasm")] -use wasm_bindgen::prelude::*; -use crate::utils::result::JsResult; +use { + crate::utils::result::JsResult, + wasm_bindgen::prelude::*, +}; use payjoin::bitcoin::address::NetworkChecked; use payjoin::UriExt; From a6e4f6d6128cf19ba2aedd1578f33d3228b3645f Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 19 Mar 2025 13:04:17 -0500 Subject: [PATCH 06/22] WIP: Receiver bindings starting with only string inputs/outputs err: getting null rust pointer in browser when trying to call .id() or .to_json() --- src/ohttp.rs | 15 +++++- src/receive/mod.rs | 5 ++ src/receive/wasm.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 src/receive/wasm.rs diff --git a/src/ohttp.rs b/src/ohttp.rs index e2ea61f..2fe418e 100644 --- a/src/ohttp.rs +++ b/src/ohttp.rs @@ -7,6 +7,8 @@ use { wasm_bindgen::prelude::*, }; +use std::str::FromStr; + impl From for OhttpKeys { fn from(value: payjoin::OhttpKeys) -> Self { Self(value) @@ -35,15 +37,24 @@ impl OhttpKeys { payjoin::OhttpKeys::decode(bytes.as_slice()).map(|e| e.into()).map_err(|e| e.into()) } + // #[cfg(feature = "wasm")] + // #[wasm_bindgen(constructor)] + // pub fn decode(bytes: Vec) -> JsResult { + // payjoin::OhttpKeys::decode(bytes.as_slice()) + // .map(|e| e.into()) + // .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + // } + #[cfg(feature = "wasm")] #[wasm_bindgen(constructor)] - pub fn decode(bytes: Vec) -> JsResult { - payjoin::OhttpKeys::decode(bytes.as_slice()) + pub fn parse(s: &str) -> JsResult { + payjoin::OhttpKeys::from_str(s) .map(|e| e.into()) .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) } } + use std::sync::Mutex; #[cfg_attr(feature = "uniffi", derive(uniffi::Object))] diff --git a/src/receive/mod.rs b/src/receive/mod.rs index 657f3df..020f4dc 100644 --- a/src/receive/mod.rs +++ b/src/receive/mod.rs @@ -12,8 +12,13 @@ use crate::{ClientResponse, Request}; #[cfg(feature = "uniffi")] pub mod uni; +#[cfg(feature = "wasm")] +pub mod wasm; + + #[derive(Clone, Debug)] pub struct Receiver(pub payjoin::receive::v2::Receiver); + impl From for payjoin::receive::v2::Receiver { fn from(value: Receiver) -> Self { value.0 diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs new file mode 100644 index 0000000..03af0d0 --- /dev/null +++ b/src/receive/wasm.rs @@ -0,0 +1,111 @@ +use std::str::FromStr; +use std::time::Duration; + +// use payjoin::bitcoin::psbt::Psbt; +// use payjoin::bitcoin::FeeRate; +// use payjoin::receive as pdk; + +use crate::bitcoin_ffi::Network; +// use crate::error::PayjoinError; +use crate::ohttp::OhttpKeys; +// use crate::uri::PjUriBuilder; +use crate::Url; + +use { + crate::utils::result::JsResult, + wasm_bindgen::prelude::*, +}; + + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct Receiver(super::Receiver); + +impl From for Receiver { + fn from(receiver: payjoin::receive::v2::Receiver) -> Self { + Self(receiver.into()) + } +} + +#[wasm_bindgen] +impl Receiver { + pub fn new( + address: String, + network: String, + directory: String, + ohttp_keys: String, + ohttp_relay: String, + expire_after: Option, + ) -> JsResult { + // Parse network string + let network = Network::from_str(&network) + .map_err(|_| wasm_bindgen::JsError::new(&format!("Invalid network: {}", network)))?; + + // Parse URLs + let directory = Url::parse(directory) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))?; + let ohttp_relay = Url::parse(ohttp_relay) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))?; + + // Parse OHTTP keys from JSON string + let ohttp_keys: OhttpKeys = OhttpKeys::parse(&ohttp_keys) + .map_err(|e| wasm_bindgen::JsError::new(&format!("{:?}", e)))?; + + // Parse Bitcoin address and verify network + let address = payjoin::bitcoin::Address::from_str(&address) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))? + .require_network(network) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))?; + + Ok(payjoin::receive::v2::Receiver::new( + address, + directory.into(), + ohttp_keys.into(), + ohttp_relay.into(), + expire_after.map(Duration::from_secs), + ) + .into()) + } + + // pub fn extract_req(&self) -> Result { + // self.0 + // .extract_req() + // .map(|(request, ctx)| RequestResponse { request, client_response: Arc::new(ctx) }) + // } + + // ///The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. + // pub fn process_res( + // &self, + // body: &[u8], + // context: Arc, + // ) -> Result>, PayjoinError> { + // >::into(self.clone()) + // .process_res(body, context.as_ref()) + // .map(|e| e.map(|x| Arc::new(x.into()))) + // } + + // pub fn pj_uri_builder(&self) -> Arc { + // Arc::new(self.0.pj_uri_builder()) + // } + // /// The contents of the `&pj=` query parameter including the base64url-encoded public key receiver subdirectory. + // /// This identifies a session at the payjoin directory server. + // #[cfg(feature = "uniffi")] + // pub fn pj_url(&self) -> Arc { + // Arc::new(self.0.pj_url()) + // } + ///The per-session public key to use as an identifier + pub fn id(&self) -> String { + self.0.id() + } + + pub fn to_json(&self) -> JsResult { + self.0.to_json() + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } + + // pub fn from_json(json: &str) -> JsResult { + // super::Receiver::from_json(json) + // .map(Into::into) + // .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + // } +} From 61cd8941678749c0dfe5cfdff102f9fcc6e7030c Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 19 Mar 2025 14:44:01 -0500 Subject: [PATCH 07/22] WIP: add web console logging --- Cargo.lock | 1 + Cargo.toml | 2 ++ src/receive/wasm.rs | 28 +++++++++++++++++++--------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9339cb..a8833fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2091,6 +2091,7 @@ dependencies = [ "uniffi", "url", "wasm-bindgen", + "web-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2e89bf4..b45e1ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ wasm = [ "getrandom", "ring", "gloo-timers", + "web-sys", ] default = ["io"] io = ["payjoin/io"] @@ -45,6 +46,7 @@ console_error_panic_hook = { version = "0.1.7", optional = true } getrandom = { version = "0.2.15", optional = true, features = ["js"] } ring = { version = "0.17.8", optional = true, features = ["wasm32_unknown_unknown_js"] } gloo-timers = { version = "0.3.0", optional = true, features = ["futures"] } +web-sys = { version = "0.3", optional = true, features = ["console"] } [dev-dependencies] bdk = { version = "0.29.0", features = ["all-keys", "use-esplora-ureq", "keys-bip39", "rpc"] } diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 03af0d0..5e9b09a 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -14,6 +14,9 @@ use crate::Url; use { crate::utils::result::JsResult, wasm_bindgen::prelude::*, + wasm_bindgen::JsValue, + web_sys::console, + // web_sys::js_sys::Date, }; @@ -36,33 +39,40 @@ impl Receiver { ohttp_keys: String, ohttp_relay: String, expire_after: Option, - ) -> JsResult { + ) -> JsResult { + // Log inputs to console + console::log_1(&JsValue::from_str(&format!( + "Receiver::new inputs: address={}, network={}, directory={}, ohttp_keys={}, ohttp_relay={}, expire_after={:?}", + address, network, directory, ohttp_keys, ohttp_relay, expire_after + ))); + // Parse network string let network = Network::from_str(&network) - .map_err(|_| wasm_bindgen::JsError::new(&format!("Invalid network: {}", network)))?; + .map_err(|_| wasm_bindgen::JsError::new("Invalid network"))?; // Parse URLs let directory = Url::parse(directory) - .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))?; + .map_err(|_| wasm_bindgen::JsError::new("Invalid directory URL"))?; let ohttp_relay = Url::parse(ohttp_relay) - .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))?; + .map_err(|_| wasm_bindgen::JsError::new("Invalid relay URL"))?; // Parse OHTTP keys from JSON string let ohttp_keys: OhttpKeys = OhttpKeys::parse(&ohttp_keys) - .map_err(|e| wasm_bindgen::JsError::new(&format!("{:?}", e)))?; + .map_err(|_| wasm_bindgen::JsError::new("Invalid OHTTP keys"))?; // Parse Bitcoin address and verify network let address = payjoin::bitcoin::Address::from_str(&address) - .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))? + .map_err(|_| wasm_bindgen::JsError::new("Invalid Bitcoin address"))? .require_network(network) - .map_err(|e| wasm_bindgen::JsError::new(&e.to_string()))?; + .map_err(|_| wasm_bindgen::JsError::new("Address network mismatch"))?; - Ok(payjoin::receive::v2::Receiver::new( + Ok(payjoin::receive::v2::Receiver::new(//new_with_time( address, directory.into(), ohttp_keys.into(), ohttp_relay.into(), - expire_after.map(Duration::from_secs), + // js_sys::Date::now(), + expire_after.map(Duration::from_secs) ) .into()) } From 46d7102cf36530873e41a9201c4f08d05e0c354a Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 19 Mar 2025 15:18:10 -0500 Subject: [PATCH 08/22] WIP: rebased --- Cargo.toml | 2 +- src/receive/wasm.rs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b45e1ff..cb7aae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ base64 = "0.22.1" bitcoin-ffi = { git = "https://github.com/bitcoindevkit/bitcoin-ffi.git", rev = "4cd8e644dbf4e001d71d5fffb232480fa5ff2246" } hex = "0.4.3" ohttp = { package = "bitcoin-ohttp", version = "0.6.0" } -payjoin = { git = "https://github.com/payjoin/rust-payjoin.git", rev = "bb47c8469146f1a9055b7f850d86f58f2b9627c6", features = ["v1", "io"] } +payjoin = { git = "https://github.com/payjoin/rust-payjoin.git", rev = "bb47c8469146f1a9055b7f850d86f58f2b9627c6", features = ["v1"] } serde_json = "1.0.128" thiserror = "1.0.58" uniffi = { version = "0.28.0", optional = true } diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 5e9b09a..9416b20 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -10,6 +10,7 @@ use crate::bitcoin_ffi::Network; use crate::ohttp::OhttpKeys; // use crate::uri::PjUriBuilder; use crate::Url; +use crate::error::PayjoinError; use { crate::utils::result::JsResult, @@ -51,8 +52,8 @@ impl Receiver { .map_err(|_| wasm_bindgen::JsError::new("Invalid network"))?; // Parse URLs - let directory = Url::parse(directory) - .map_err(|_| wasm_bindgen::JsError::new("Invalid directory URL"))?; + // let directory = Url::parse(directory) + // .map_err(|_| wasm_bindgen::JsError::new("Invalid directory URL"))?; let ohttp_relay = Url::parse(ohttp_relay) .map_err(|_| wasm_bindgen::JsError::new("Invalid relay URL"))?; @@ -66,14 +67,14 @@ impl Receiver { .require_network(network) .map_err(|_| wasm_bindgen::JsError::new("Address network mismatch"))?; - Ok(payjoin::receive::v2::Receiver::new(//new_with_time( + Ok(payjoin::receive::v2::Receiver::new( address, - directory.into(), + directory, ohttp_keys.into(), - ohttp_relay.into(), - // js_sys::Date::now(), + // Date::now(), expire_after.map(Duration::from_secs) ) + .map_err(PayjoinError::from)? .into()) } From f78480210297d09f364294cb3ffea2a6fdd3be3e Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 19 Mar 2025 15:51:49 -0500 Subject: [PATCH 09/22] Init a Receiver, use js_sys::Date Need to pass in system time from JS conotext for WASM todo: turst current time logic into a helper method --- Cargo.lock | 1 - Cargo.toml | 2 +- src/receive/wasm.rs | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8833fd..c9abeba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2023,7 +2023,6 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "payjoin" version = "0.22.0" -source = "git+https://github.com/payjoin/rust-payjoin.git?rev=bb47c8469146f1a9055b7f850d86f58f2b9627c6#bb47c8469146f1a9055b7f850d86f58f2b9627c6" dependencies = [ "bhttp", "bitcoin 0.32.5", diff --git a/Cargo.toml b/Cargo.toml index cb7aae5..d69c618 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ base64 = "0.22.1" bitcoin-ffi = { git = "https://github.com/bitcoindevkit/bitcoin-ffi.git", rev = "4cd8e644dbf4e001d71d5fffb232480fa5ff2246" } hex = "0.4.3" ohttp = { package = "bitcoin-ohttp", version = "0.6.0" } -payjoin = { git = "https://github.com/payjoin/rust-payjoin.git", rev = "bb47c8469146f1a9055b7f850d86f58f2b9627c6", features = ["v1"] } +payjoin = { path = "../../rust-payjoin/payjoin", features = ["v1"] } serde_json = "1.0.128" thiserror = "1.0.58" uniffi = { version = "0.28.0", optional = true } diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 9416b20..a8a3a4c 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -17,7 +17,7 @@ use { wasm_bindgen::prelude::*, wasm_bindgen::JsValue, web_sys::console, - // web_sys::js_sys::Date, + web_sys::js_sys::Date, }; @@ -67,11 +67,11 @@ impl Receiver { .require_network(network) .map_err(|_| wasm_bindgen::JsError::new("Address network mismatch"))?; - Ok(payjoin::receive::v2::Receiver::new( + Ok(payjoin::receive::v2::Receiver::new_with_time( address, directory, ohttp_keys.into(), - // Date::now(), + std::time::SystemTime::UNIX_EPOCH + Duration::from_millis(Date::now() as u64), expire_after.map(Duration::from_secs) ) .map_err(PayjoinError::from)? From ea5559155a88e695282e63c0d01f6d3b9090904d Mon Sep 17 00:00:00 2001 From: River Kanies Date: Fri, 21 Mar 2025 15:13:25 -0500 Subject: [PATCH 10/22] WIP: sender step 1 --- Cargo.lock | 2 ++ src/lib.rs | 2 ++ src/receive/mod.rs | 2 +- src/receive/wasm.rs | 13 ++++--- src/request_wasm.rs | 33 ++++++++++++++++++ src/send/mod.rs | 3 ++ src/send/wasm.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 src/request_wasm.rs create mode 100644 src/send/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index c9abeba..1aa00cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2036,6 +2036,7 @@ dependencies = [ "serde", "serde_json", "url", + "web-time", ] [[package]] @@ -3686,6 +3687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", + "serde", "wasm-bindgen", ] diff --git a/src/lib.rs b/src/lib.rs index b90100d..c7c1654 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ pub mod io; pub mod ohttp; pub mod receive; pub mod request; +#[cfg(feature = "wasm")] +pub mod request_wasm; pub mod send; pub mod uri; mod utils; diff --git a/src/receive/mod.rs b/src/receive/mod.rs index 020f4dc..7706ac2 100644 --- a/src/receive/mod.rs +++ b/src/receive/mod.rs @@ -251,7 +251,7 @@ impl WantsOutputs { replacement_outputs.iter().map(|o| o.clone().into()).collect(); self.0 .clone() - .replace_receiver_outputs(replacement_outputs, &drain_script.0) + .replace_receiver_outputs(replacement_outputs.into_iter(), &drain_script.0) .map(Into::into) .map_err(Into::into) } diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index a8a3a4c..54c397b 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -8,7 +8,6 @@ use std::time::Duration; use crate::bitcoin_ffi::Network; // use crate::error::PayjoinError; use crate::ohttp::OhttpKeys; -// use crate::uri::PjUriBuilder; use crate::Url; use crate::error::PayjoinError; @@ -17,7 +16,7 @@ use { wasm_bindgen::prelude::*, wasm_bindgen::JsValue, web_sys::console, - web_sys::js_sys::Date, + // web_sys::js_sys::Date, }; @@ -67,17 +66,20 @@ impl Receiver { .require_network(network) .map_err(|_| wasm_bindgen::JsError::new("Address network mismatch"))?; - Ok(payjoin::receive::v2::Receiver::new_with_time( + Ok(payjoin::receive::v2::Receiver::new( address, directory, ohttp_keys.into(), - std::time::SystemTime::UNIX_EPOCH + Duration::from_millis(Date::now() as u64), expire_after.map(Duration::from_secs) ) .map_err(PayjoinError::from)? .into()) } + pub fn pj_uri(&self) -> crate::PjUri { + self.0.pj_uri().into() + } + // pub fn extract_req(&self) -> Result { // self.0 // .extract_req() @@ -95,9 +97,6 @@ impl Receiver { // .map(|e| e.map(|x| Arc::new(x.into()))) // } - // pub fn pj_uri_builder(&self) -> Arc { - // Arc::new(self.0.pj_uri_builder()) - // } // /// The contents of the `&pj=` query parameter including the base64url-encoded public key receiver subdirectory. // /// This identifies a session at the payjoin directory server. // #[cfg(feature = "uniffi")] diff --git a/src/request_wasm.rs b/src/request_wasm.rs new file mode 100644 index 0000000..8f39fe2 --- /dev/null +++ b/src/request_wasm.rs @@ -0,0 +1,33 @@ +use wasm_bindgen::prelude::*; + +/// Represents data that needs to be transmitted to the receiver. +/// You need to send this request over HTTP(S) to the receiver. +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct Request { + /// URL to send the request to. + /// + /// This is full URL with scheme etc - you can pass it right to `reqwest` or a similar library. + url: String, + + /// The `Content-Type` header to use for the request. + /// + /// `text/plain` for v1 requests and `message/ohttp-req` for v2 requests. + content_type: String, + + /// Bytes to be sent to the receiver. + /// + /// This is properly encoded PSBT payload either in base64 in v1 or an OHTTP encapsulated payload in v2. + body: Vec, +} + + +impl From for Request { + fn from(value: payjoin::Request) -> Self { + Self { + url: value.url.to_string(), // Convert Url to String explicitly + content_type: value.content_type.to_string(), + body: value.body, + } + } +} \ No newline at end of file diff --git a/src/send/mod.rs b/src/send/mod.rs index cd564d3..c2c8456 100644 --- a/src/send/mod.rs +++ b/src/send/mod.rs @@ -10,6 +10,9 @@ use crate::uri::{PjUri, Url}; #[cfg(feature = "uniffi")] pub mod uni; +#[cfg(feature = "wasm")] +pub mod wasm; + ///Builder for sender-side payjoin parameters /// ///These parameters define how client wants to handle Payjoin. diff --git a/src/send/wasm.rs b/src/send/wasm.rs new file mode 100644 index 0000000..4de1b01 --- /dev/null +++ b/src/send/wasm.rs @@ -0,0 +1,83 @@ +use std::str::FromStr; +use std::time::Duration; + +// use payjoin::bitcoin::psbt::Psbt; +// use payjoin::bitcoin::FeeRate; +// use payjoin::receive as pdk; + +use crate::bitcoin_ffi::Network; +// use crate::error::PayjoinError; +use crate::ohttp::OhttpKeys; +use crate::Url; +use crate::error::PayjoinError; + +use { + crate::utils::result::JsResult, + wasm_bindgen::prelude::*, + wasm_bindgen::JsValue, + web_sys::console, + // web_sys::js_sys::Date, +}; +use crate::uri::PjUri; +use crate::request_wasm::Request; + +#[wasm_bindgen] +#[derive(Clone)] +pub struct SenderBuilder(super::SenderBuilder); + +impl From> for SenderBuilder { + fn from(sender: payjoin::send::v2::SenderBuilder<'static>) -> Self { + Self(super::SenderBuilder::from(sender)) + } +} + +impl From for SenderBuilder { + fn from(sender: super::SenderBuilder) -> Self { + Self(sender) + } +} + +#[wasm_bindgen] +impl SenderBuilder { + pub fn from_psbt_and_uri(psbt: String, uri: PjUri) -> JsResult { + super::SenderBuilder::from_psbt_and_uri(psbt, uri) + .map(Into::into) + .map_err(Into::into) + } + + pub fn build_recommended(&self, min_fee_rate: u64) -> JsResult { + self.0 + .clone() + .build_recommended(min_fee_rate) + .map(Into::into) + .map_err(Into::into) + } +} + + +#[wasm_bindgen] +#[derive(Clone)] +pub struct Sender(payjoin::send::v2::Sender); + +impl From for Sender { + fn from(value: payjoin::send::v2::Sender) -> Self { + Self(value) + } +} + +impl From for Sender { + fn from(sender: super::Sender) -> Self { + Self(sender.0) + } +} +#[wasm_bindgen] +impl Sender { + /// Extract serialized Request and Context from a Payjoin Proposal. + pub fn extract_v2(&self, ohttp_relay: String) -> JsResult {//(Request, V2PostContext)> { + let url = Url::parse(ohttp_relay)?; + match self.0.extract_v2(url.into()) { + Ok((req, _ctx)) => Ok(req.into()),//(req.into(), ctx.into())), + Err(e) => Err(e.into()), + } + } +} \ No newline at end of file From bce40e2bedb60ca04808cdaec21554d87fa1cc69 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Fri, 21 Mar 2025 16:31:08 -0500 Subject: [PATCH 11/22] WIP: properly expose Request attrs wrap PdkRequest and make js getters err: currently getting a timeout error when actually making the request, probably the url or body is the issue --- src/request_wasm.rs | 37 ++++++++++++++++++------------------- src/send/wasm.rs | 1 + 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/request_wasm.rs b/src/request_wasm.rs index 8f39fe2..f1cac02 100644 --- a/src/request_wasm.rs +++ b/src/request_wasm.rs @@ -1,33 +1,32 @@ use wasm_bindgen::prelude::*; +use payjoin::Request as PdkRequest; /// Represents data that needs to be transmitted to the receiver. /// You need to send this request over HTTP(S) to the receiver. #[wasm_bindgen] #[derive(Clone, Debug)] -pub struct Request { - /// URL to send the request to. - /// - /// This is full URL with scheme etc - you can pass it right to `reqwest` or a similar library. - url: String, +pub struct Request(PdkRequest); - /// The `Content-Type` header to use for the request. - /// - /// `text/plain` for v1 requests and `message/ohttp-req` for v2 requests. - content_type: String, +#[wasm_bindgen] +impl Request { + #[wasm_bindgen(getter)] + pub fn url(&self) -> String { + self.0.url.to_string() + } - /// Bytes to be sent to the receiver. - /// - /// This is properly encoded PSBT payload either in base64 in v1 or an OHTTP encapsulated payload in v2. - body: Vec, -} + #[wasm_bindgen(getter)] + pub fn content_type(&self) -> String { + self.0.content_type.to_string() + } + #[wasm_bindgen(getter)] + pub fn body(&self) -> Vec { + self.0.body.clone() + } +} impl From for Request { fn from(value: payjoin::Request) -> Self { - Self { - url: value.url.to_string(), // Convert Url to String explicitly - content_type: value.content_type.to_string(), - body: value.body, - } + Self(value) } } \ No newline at end of file diff --git a/src/send/wasm.rs b/src/send/wasm.rs index 4de1b01..d655e36 100644 --- a/src/send/wasm.rs +++ b/src/send/wasm.rs @@ -40,6 +40,7 @@ impl From for SenderBuilder { #[wasm_bindgen] impl SenderBuilder { pub fn from_psbt_and_uri(psbt: String, uri: PjUri) -> JsResult { + console::log_1(&JsValue::from_str(&format!("SenderBuilder::from_psbt_and_uri: psbt={}, uri={}", psbt, uri.as_string()))); super::SenderBuilder::from_psbt_and_uri(psbt, uri) .map(Into::into) .map_err(Into::into) From 3a1b489473b866e0b369228747d4082fea9d7d51 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 26 Mar 2025 13:10:56 -0500 Subject: [PATCH 12/22] WIP: send post and get contexts --- src/send/wasm.rs | 101 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/src/send/wasm.rs b/src/send/wasm.rs index d655e36..fcd0524 100644 --- a/src/send/wasm.rs +++ b/src/send/wasm.rs @@ -1,15 +1,6 @@ -use std::str::FromStr; -use std::time::Duration; +use crate::ohttp::ClientResponse; -// use payjoin::bitcoin::psbt::Psbt; -// use payjoin::bitcoin::FeeRate; -// use payjoin::receive as pdk; - -use crate::bitcoin_ffi::Network; -// use crate::error::PayjoinError; -use crate::ohttp::OhttpKeys; use crate::Url; -use crate::error::PayjoinError; use { crate::utils::result::JsResult, @@ -74,11 +65,93 @@ impl From for Sender { #[wasm_bindgen] impl Sender { /// Extract serialized Request and Context from a Payjoin Proposal. - pub fn extract_v2(&self, ohttp_relay: String) -> JsResult {//(Request, V2PostContext)> { - let url = Url::parse(ohttp_relay)?; + pub fn extract_v2(&self, ohttp_relay: String) -> JsResult { + let url = Url::parse(ohttp_relay)?; match self.0.extract_v2(url.into()) { - Ok((req, _ctx)) => Ok(req.into()),//(req.into(), ctx.into())), + Ok((req, ctx)) => Ok(RequestV2PostContext::new(req.into(), ctx.into())), Err(e) => Err(e.into()), } } -} \ No newline at end of file +} + +#[wasm_bindgen] +pub struct V2PostContext(payjoin::send::v2::V2PostContext); + +impl From for V2PostContext { + fn from(ctx: payjoin::send::v2::V2PostContext) -> Self { + Self(ctx) + } +} + +#[wasm_bindgen] +impl V2PostContext { + // consumes self, so V2PostContext won't be available in js after calling this + pub fn process_response(self, response: &[u8]) -> JsResult { + self.0.process_response(response) + .map(Into::into) + .map_err(Into::into) + } +} + +#[wasm_bindgen] +pub struct V2GetContext(payjoin::send::v2::V2GetContext); + +impl From for V2GetContext { + fn from(ctx: payjoin::send::v2::V2GetContext) -> Self { + Self(ctx) + } +} + +#[wasm_bindgen] +impl V2GetContext { + pub fn extract_req(&self, ohttp_relay: String) -> JsResult { + self.0.extract_req(payjoin::Url::parse(&ohttp_relay)?) + .map(|(request, ctx)| RequestOhttpContext::new(request.into(), ctx.into())) + .map_err(Into::into) + } + + pub fn process_response(&self, response: &[u8], ohttp_ctx: &ClientResponse) -> JsResult> { + self.0.process_response(response, ohttp_ctx.into()) + .map(|opt_psbt| opt_psbt.map(|psbt| psbt.to_string())) + .map_err(Into::into) + } +} + +#[wasm_bindgen] +pub struct RequestOhttpContext (Request, ClientResponse); + +impl RequestOhttpContext { + pub fn new(request: Request, ohttp_ctx: ClientResponse) -> Self { + Self(request, ohttp_ctx) + } + + pub fn request(&self) -> Request { + self.0.clone() + } + + pub fn ohttp_ctx(self) -> ClientResponse { + self.1 + } +} + +#[wasm_bindgen] +pub struct RequestV2PostContext (Request, V2PostContext); + +#[wasm_bindgen] +impl RequestV2PostContext { + #[wasm_bindgen(constructor)] + pub fn new(request: Request, context: V2PostContext) -> Self { + Self(request, context) + } + + #[wasm_bindgen(getter)] + pub fn request(&self) -> Request { + self.0.clone() // Assuming Request implements Clone + } + + // consumes self, so RequestV2PostContext won't be available in js after getting context, however, using destructuring on the js end makes this seemless. + #[wasm_bindgen(getter)] + pub fn context(self) -> V2PostContext { + self.1 + } +} From 2518895da84b72dc3691c666884db4eb8cc131b1 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Thu, 27 Mar 2025 12:21:05 -0500 Subject: [PATCH 13/22] WIP: Receiver.extract_req --- src/receive/wasm.rs | 37 +++++++++++++++++++++++++++++++------ src/request_wasm.rs | 30 ++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 54c397b..a8aa338 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -7,7 +7,9 @@ use std::time::Duration; use crate::bitcoin_ffi::Network; // use crate::error::PayjoinError; -use crate::ohttp::OhttpKeys; + +use crate::ohttp::{ClientResponse, OhttpKeys}; +use crate::request_wasm::Request; use crate::Url; use crate::error::PayjoinError; @@ -80,11 +82,12 @@ impl Receiver { self.0.pj_uri().into() } - // pub fn extract_req(&self) -> Result { - // self.0 - // .extract_req() - // .map(|(request, ctx)| RequestResponse { request, client_response: Arc::new(ctx) }) - // } + pub fn extract_req(&self, ohttp_relay: String) -> JsResult { + self.0 + .extract_req(ohttp_relay) + .map(|(request, ctx)| RequestResponse::new(request.into(), ctx)) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } // ///The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. // pub fn process_res( @@ -119,3 +122,25 @@ impl Receiver { // .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) // } } + +#[wasm_bindgen] +pub struct RequestResponse(Request, ClientResponse); + +#[wasm_bindgen] +impl RequestResponse { + #[wasm_bindgen(constructor)] + pub fn new(request: Request, client_response: ClientResponse) -> Self { + Self(request, client_response) + } + + #[wasm_bindgen(getter)] + pub fn request(&self) -> Request { + self.0.clone() + } + + // consumes self, so RequestResponse won't be available in js after getting client_response + #[wasm_bindgen(getter)] + pub fn client_response(self) -> ClientResponse { + self.1 + } +} diff --git a/src/request_wasm.rs b/src/request_wasm.rs index f1cac02..619f1ce 100644 --- a/src/request_wasm.rs +++ b/src/request_wasm.rs @@ -1,32 +1,50 @@ use wasm_bindgen::prelude::*; -use payjoin::Request as PdkRequest; +use crate::request::Request as PdkRequest; /// Represents data that needs to be transmitted to the receiver. /// You need to send this request over HTTP(S) to the receiver. #[wasm_bindgen] #[derive(Clone, Debug)] -pub struct Request(PdkRequest); +pub struct Request { + url: String, + content_type: String, + body: Vec +} #[wasm_bindgen] impl Request { #[wasm_bindgen(getter)] pub fn url(&self) -> String { - self.0.url.to_string() + self.url.clone() } #[wasm_bindgen(getter)] pub fn content_type(&self) -> String { - self.0.content_type.to_string() + self.content_type.clone() } #[wasm_bindgen(getter)] pub fn body(&self) -> Vec { - self.0.body.clone() + self.body.clone() } } impl From for Request { fn from(value: payjoin::Request) -> Self { - Self(value) + Self { + url: value.url.to_string(), + content_type: value.content_type.to_string(), + body: value.body, + } + } +} + +impl From for Request { + fn from(value: PdkRequest) -> Self { + Self { + url: value.url.as_ref().as_string(), + content_type: value.content_type.to_string(), + body: value.body, + } } } \ No newline at end of file From 96c10107a7b2bf210d5a41c78ebbcf28fb678a50 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Thu, 27 Mar 2025 13:11:14 -0500 Subject: [PATCH 14/22] WIP: Receiver: UncheckedProposal and MaybeInputsOwned minimal implementations, assume happy path for now --- src/receive/wasm.rs | 59 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index a8aa338..35febfe 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -89,16 +89,17 @@ impl Receiver { .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) } - // ///The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. - // pub fn process_res( - // &self, - // body: &[u8], - // context: Arc, - // ) -> Result>, PayjoinError> { - // >::into(self.clone()) - // .process_res(body, context.as_ref()) - // .map(|e| e.map(|x| Arc::new(x.into()))) - // } + ///The response can either be an UncheckedProposal or an ACCEPTED message indicating no UncheckedProposal is available yet. + pub fn process_res( + &self, + body: &[u8], + context: ClientResponse, + ) -> JsResult> { + self.0 + .process_res(body, &context) + .map(|e| e.map(|x| x.into())) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } // /// The contents of the `&pj=` query parameter including the base64url-encoded public key receiver subdirectory. // /// This identifies a session at the payjoin directory server. @@ -144,3 +145,41 @@ impl RequestResponse { self.1 } } + +#[derive(Clone)] +#[wasm_bindgen] +pub struct UncheckedProposal(super::UncheckedProposal); + +impl From for UncheckedProposal { + fn from(value: super::UncheckedProposal) -> Self { + Self(value) + } +} + +#[wasm_bindgen] +impl UncheckedProposal { + pub fn check_broadcast_suitability( + &self, + min_fee_rate: Option, + can_broadcast: Option,//Box,//fn to check tx can broadcast + ) -> JsResult { + self.0 + .clone() + .check_broadcast_suitability(min_fee_rate, |transaction| { + // should actually check if the transaction can be broadcast + Ok(can_broadcast.unwrap_or(false)) + }) + .map(|e| e.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } +} + +#[derive(Clone)] +#[wasm_bindgen] +pub struct MaybeInputsOwned(super::MaybeInputsOwned); + +impl From for MaybeInputsOwned { + fn from(value: super::MaybeInputsOwned) -> Self { + Self(value) + } +} \ No newline at end of file From 4759eb887de37771b57a1698d6c62cb7bd3667a2 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Thu, 27 Mar 2025 15:25:12 -0500 Subject: [PATCH 15/22] WIP: using js callback in wasm binding for pj checks --- src/receive/wasm.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 35febfe..aaf2346 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -18,6 +18,7 @@ use { wasm_bindgen::prelude::*, wasm_bindgen::JsValue, web_sys::console, + web_sys::js_sys, // web_sys::js_sys::Date, }; @@ -182,4 +183,38 @@ impl From for MaybeInputsOwned { fn from(value: super::MaybeInputsOwned) -> Self { Self(value) } +} + +#[wasm_bindgen] +impl MaybeInputsOwned { + ///Check that the Original PSBT has no receiver-owned inputs. Return original-psbt-rejected error or otherwise refuse to sign undesirable inputs. + /// An attacker could try to spend receiver's own inputs. This check prevents that. + pub fn check_inputs_not_owned( + &self, + is_owned: js_sys::Function, + ) -> JsResult { + self.0 + .check_inputs_not_owned(|input| { + let result = is_owned.call1(&JsValue::NULL, &js_sys::Uint8Array::from(&input[..])) + .map_err(|e| PayjoinError::UnexpectedError { + message: e.as_string().unwrap_or_else(|| "Unknown JS error".to_string()) + })?; + result.as_bool() + .ok_or_else(|| PayjoinError::UnexpectedError { + message: "Function must return boolean".to_string() + }) + }) + .map(|t| t.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } +} + +#[derive(Clone)] +#[wasm_bindgen] +pub struct MaybeInputsSeen(super::MaybeInputsSeen); + +impl From for MaybeInputsSeen { + fn from(value: super::MaybeInputsSeen) -> Self { + Self(value) + } } \ No newline at end of file From fd4e967ca396c9d7cffe224ba409d9e3130be635 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Thu, 27 Mar 2025 17:52:48 -0500 Subject: [PATCH 16/22] WIP: begin adding inputs partial implementation --- Cargo.lock | 13 +++++ Cargo.toml | 2 + src/receive/wasm.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1aa00cb..3d1af1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2083,6 +2083,7 @@ dependencies = [ "reqwest", "ring 0.17.8", "rustls 0.22.4", + "serde-wasm-bindgen", "serde_json", "testcontainers", "testcontainers-modules", @@ -2720,6 +2721,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515286919c7a28f96c362f42cd42aec7ef6d417334118be69d1ad96160aa5f5" +dependencies = [ + "fnv", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.215" diff --git a/Cargo.toml b/Cargo.toml index d69c618..d195791 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ wasm = [ "ring", "gloo-timers", "web-sys", + "serde-wasm-bindgen", ] default = ["io"] io = ["payjoin/io"] @@ -47,6 +48,7 @@ getrandom = { version = "0.2.15", optional = true, features = ["js"] } ring = { version = "0.17.8", optional = true, features = ["wasm32_unknown_unknown_js"] } gloo-timers = { version = "0.3.0", optional = true, features = ["futures"] } web-sys = { version = "0.3", optional = true, features = ["console"] } +serde-wasm-bindgen = { version = "0.2.0", optional = true } [dev-dependencies] bdk = { version = "0.29.0", features = ["all-keys", "use-esplora-ureq", "keys-bip39", "rpc"] } diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index aaf2346..b4843cc 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -20,6 +20,7 @@ use { web_sys::console, web_sys::js_sys, // web_sys::js_sys::Date, + serde_wasm_bindgen, }; @@ -217,4 +218,121 @@ impl From for MaybeInputsSeen { fn from(value: super::MaybeInputsSeen) -> Self { Self(value) } +} + +#[wasm_bindgen] +impl MaybeInputsSeen { + /// Make sure that the original transaction inputs have never been seen before. This prevents probing attacks. This prevents reentrant Payjoin, where a sender proposes a Payjoin PSBT as a new Original PSBT for a new Payjoin. + pub fn check_no_inputs_seen_before( + &self, + is_known: js_sys::Function, + ) -> JsResult { + self.0 + .clone() + .check_no_inputs_seen_before(|outpoint| { + // Convert the outpoint to a JsValue and call the callback + is_known.call1(&JsValue::null(), &serde_wasm_bindgen::to_value(outpoint).map_err(|e| PayjoinError::UnexpectedError { + message: e.to_string(), + })?) + .map(|result| result.as_bool().unwrap_or(false)) + .map(Ok) + .unwrap_or(Ok(false)) + }) + .map(|t| t.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } +} + +#[derive(Clone)] +#[wasm_bindgen] +pub struct OutputsUnknown(super::OutputsUnknown); + +impl From for OutputsUnknown { + fn from(value: super::OutputsUnknown) -> Self { + Self(value) + } +} + +#[wasm_bindgen] +impl OutputsUnknown { + /// Find which outputs belong to the receiver + pub fn identify_receiver_outputs( + &self, + is_receiver_output: js_sys::Function, + ) -> JsResult { + self.0 + .clone() + .identify_receiver_outputs(|output_script| { + is_receiver_output.call1(&JsValue::null(), &js_sys::Uint8Array::from(&output_script[..])) + .map(|result| result.as_bool().unwrap_or(false)) + .map_err(|e| PayjoinError::UnexpectedError { + message: e.as_string().unwrap_or_else(|| "Unknown JS error".to_string()) + }) + }) + .map(|t| t.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } +} + +#[wasm_bindgen] +pub struct WantsOutputs(super::WantsOutputs); + +impl From for WantsOutputs { + fn from(value: super::WantsOutputs) -> Self { + Self(value) + } +} + +#[wasm_bindgen] +impl WantsOutputs { + pub fn commit_outputs(&self) -> WantsInputs { + self.0.commit_outputs().into() + } +} + +#[wasm_bindgen] +pub struct WantsInputs(super::WantsInputs); + +impl From for WantsInputs { + fn from(value: super::WantsInputs) -> Self { + Self(value) + } +} +#[wasm_bindgen] +impl WantsInputs { + + pub fn contribute_inputs( + &self, + replacement_inputs: Vec, + ) -> JsResult { + let replacement_inputs: Vec = replacement_inputs + .into_iter() + .map(|pair| pair.0) + .collect(); + self.0.contribute_inputs(replacement_inputs) + .map(|t| t.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } + + pub fn commit_inputs(&self) -> ProvisionalProposal { + self.0.commit_inputs().into() + } +} + +#[wasm_bindgen] +pub struct InputPair(super::InputPair); + +impl From for InputPair { + fn from(value: super::InputPair) -> Self { + Self(value) + } +} + +#[wasm_bindgen] +pub struct ProvisionalProposal(super::ProvisionalProposal); + +impl From for ProvisionalProposal { + fn from(value: super::ProvisionalProposal) -> Self { + Self(value) + } } \ No newline at end of file From 9652c608d2519060226d0c820c683656af2b89e8 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Fri, 28 Mar 2025 17:19:54 -0500 Subject: [PATCH 17/22] WIP: InputPair --- src/receive/wasm.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index b4843cc..83d944d 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -1,5 +1,6 @@ use std::str::FromStr; use std::time::Duration; +use std::sync::Arc; // use payjoin::bitcoin::psbt::Psbt; // use payjoin::bitcoin::FeeRate; @@ -328,6 +329,35 @@ impl From for InputPair { } } +#[wasm_bindgen] +impl InputPair { + pub fn new( + txid: String, + vout: u32, + value: u64, + script_pubkey: String, + ) -> Self { + let txin = bitcoin_ffi::TxIn { + previous_output: bitcoin_ffi::OutPoint { + txid: bitcoin_ffi::Txid::from_str(&txid).unwrap(), + vout, + }, + script_sig: Arc::new(bitcoin_ffi::Script::new(Vec::new())), + sequence: 0xffffffff, + witness: Vec::new(), + }; + let psbtin = crate::bitcoin_ffi::PsbtInput { + witness_utxo: Some(bitcoin_ffi::TxOut { + value: Arc::new(bitcoin_ffi::Amount::from_sat(value)), + script_pubkey: Arc::new(bitcoin_ffi::Script::new(script_pubkey.into())), + }), + redeem_script: None, + witness_script: None, + }; + Self(super::InputPair::new(txin, psbtin).unwrap()) + } +} + #[wasm_bindgen] pub struct ProvisionalProposal(super::ProvisionalProposal); From 67e0a9bcf21069782a4c0c4676495c6d12aabeb9 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Mon, 31 Mar 2025 14:31:16 -0500 Subject: [PATCH 18/22] WIP: fix InputPair script to be bytes --- src/receive/wasm.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 83d944d..6318392 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -335,7 +335,7 @@ impl InputPair { txid: String, vout: u32, value: u64, - script_pubkey: String, + script_pubkey: Vec, ) -> Self { let txin = bitcoin_ffi::TxIn { previous_output: bitcoin_ffi::OutPoint { @@ -346,10 +346,11 @@ impl InputPair { sequence: 0xffffffff, witness: Vec::new(), }; + // console::log_1(&JsValue::from_str(&format!("InputPair::new: txid={}, vout={}, value={}, script_pubkey={}", txid, vout, value, script_pubkey))); let psbtin = crate::bitcoin_ffi::PsbtInput { witness_utxo: Some(bitcoin_ffi::TxOut { value: Arc::new(bitcoin_ffi::Amount::from_sat(value)), - script_pubkey: Arc::new(bitcoin_ffi::Script::new(script_pubkey.into())), + script_pubkey: Arc::new(bitcoin_ffi::Script::new(script_pubkey)), }), redeem_script: None, witness_script: None, From 945e6746f32a65fa6ddeac8ea5072712eefe21c8 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Mon, 31 Mar 2025 16:15:41 -0500 Subject: [PATCH 19/22] WIP: finalize proposal --- src/receive/wasm.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 6318392..0dabcb1 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -366,4 +366,48 @@ impl From for ProvisionalProposal { fn from(value: super::ProvisionalProposal) -> Self { Self(value) } +} + +#[wasm_bindgen] +impl ProvisionalProposal { + pub fn finalize_proposal( + &self, + process_psbt: js_sys::Function, + min_feerate_sat_per_vb: Option, + max_effective_fee_rate_sat_per_vb: Option, + ) -> JsResult { + self.0 + .finalize_proposal( + |psbt| { + process_psbt.call1(&JsValue::null(), &JsValue::from_str(&psbt.to_string())) + .map_err(|e| PayjoinError::UnexpectedError { + message: e.as_string().unwrap_or_else(|| "Unknown JS error".to_string()) + })? + .as_string() + .ok_or_else(|| PayjoinError::UnexpectedError { + message: "Process PSBT must return string".to_string() + }) + }, + min_feerate_sat_per_vb, + max_effective_fee_rate_sat_per_vb, + ) + .map(|e| e.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } +} + +#[derive(Clone)] +#[wasm_bindgen] +pub struct PayjoinProposal(super::PayjoinProposal); + +impl From for super::PayjoinProposal { + fn from(value: PayjoinProposal) -> Self { + value.0 + } +} + +impl From for PayjoinProposal { + fn from(value: super::PayjoinProposal) -> Self { + Self(value) + } } \ No newline at end of file From 0ad877f91179800b78fe6cde4b92f6205f273075 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Mon, 31 Mar 2025 16:38:34 -0500 Subject: [PATCH 20/22] WIP: submit final proposal --- src/receive/wasm.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 0dabcb1..1d5d1f7 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -410,4 +410,27 @@ impl From for PayjoinProposal { fn from(value: super::PayjoinProposal) -> Self { Self(value) } +} + +#[wasm_bindgen] +impl PayjoinProposal { + + pub fn extract_v2_req( + &self, + ohttp_relay: String, + ) -> JsResult { + match self.0.clone().extract_v2_req(ohttp_relay) { + Ok((req, ctx)) => Ok(RequestResponse::new(req.into(), ctx.into())), + Err(e) => Err(wasm_bindgen::JsError::new(&e.to_string())) + } + } + + pub fn process_res( + &self, + body: &[u8], + ohttp_context: &ClientResponse, + ) -> JsResult<()> { + self.0.process_res(body, ohttp_context.into()) + .map_err(|e| e.into()) + } } \ No newline at end of file From 1ca33df084694f34e0a7412193cd5a1ab595c292 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Tue, 1 Apr 2025 14:49:25 -0500 Subject: [PATCH 21/22] Demo Done: fix RequestOhttpContext the browser demo is fully working now only supports v2 doesn't yet fully validate checks limited options --- src/send/wasm.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/send/wasm.rs b/src/send/wasm.rs index fcd0524..e25bfe0 100644 --- a/src/send/wasm.rs +++ b/src/send/wasm.rs @@ -120,15 +120,18 @@ impl V2GetContext { #[wasm_bindgen] pub struct RequestOhttpContext (Request, ClientResponse); +#[wasm_bindgen] impl RequestOhttpContext { pub fn new(request: Request, ohttp_ctx: ClientResponse) -> Self { Self(request, ohttp_ctx) } + #[wasm_bindgen(getter)] pub fn request(&self) -> Request { self.0.clone() } + #[wasm_bindgen(getter)] pub fn ohttp_ctx(self) -> ClientResponse { self.1 } From e2ef422f0a8a459e15a8b333ac56aff2b20f8e22 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Mon, 21 Apr 2025 13:25:26 -0500 Subject: [PATCH 22/22] WASM: OhttpKeys as input to Receiver.new Required enabling OhttpKeys constructor from bytes --- src/ohttp.rs | 15 +++++++-------- src/receive/wasm.rs | 16 +++++----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/ohttp.rs b/src/ohttp.rs index 2fe418e..dd51bdf 100644 --- a/src/ohttp.rs +++ b/src/ohttp.rs @@ -37,16 +37,15 @@ impl OhttpKeys { payjoin::OhttpKeys::decode(bytes.as_slice()).map(|e| e.into()).map_err(|e| e.into()) } - // #[cfg(feature = "wasm")] - // #[wasm_bindgen(constructor)] - // pub fn decode(bytes: Vec) -> JsResult { - // payjoin::OhttpKeys::decode(bytes.as_slice()) - // .map(|e| e.into()) - // .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) - // } - #[cfg(feature = "wasm")] #[wasm_bindgen(constructor)] + pub fn decode(bytes: Vec) -> JsResult { + payjoin::OhttpKeys::decode(bytes.as_slice()) + .map(|e| e.into()) + .map_err(|e| wasm_bindgen::JsError::new(&e.to_string())) + } + + #[cfg(feature = "wasm")] pub fn parse(s: &str) -> JsResult { payjoin::OhttpKeys::from_str(s) .map(|e| e.into()) diff --git a/src/receive/wasm.rs b/src/receive/wasm.rs index 1d5d1f7..c7cea9f 100644 --- a/src/receive/wasm.rs +++ b/src/receive/wasm.rs @@ -41,15 +41,9 @@ impl Receiver { address: String, network: String, directory: String, - ohttp_keys: String, - ohttp_relay: String, + ohttp_keys: OhttpKeys, expire_after: Option, ) -> JsResult { - // Log inputs to console - console::log_1(&JsValue::from_str(&format!( - "Receiver::new inputs: address={}, network={}, directory={}, ohttp_keys={}, ohttp_relay={}, expire_after={:?}", - address, network, directory, ohttp_keys, ohttp_relay, expire_after - ))); // Parse network string let network = Network::from_str(&network) @@ -58,12 +52,12 @@ impl Receiver { // Parse URLs // let directory = Url::parse(directory) // .map_err(|_| wasm_bindgen::JsError::new("Invalid directory URL"))?; - let ohttp_relay = Url::parse(ohttp_relay) - .map_err(|_| wasm_bindgen::JsError::new("Invalid relay URL"))?; + // let ohttp_relay = Url::parse(ohttp_relay) + // .map_err(|_| wasm_bindgen::JsError::new("Invalid relay URL"))?; // Parse OHTTP keys from JSON string - let ohttp_keys: OhttpKeys = OhttpKeys::parse(&ohttp_keys) - .map_err(|_| wasm_bindgen::JsError::new("Invalid OHTTP keys"))?; + // let ohttp_keys: OhttpKeys = OhttpKeys::parse(&ohttp_keys) + // .map_err(|_| wasm_bindgen::JsError::new("Invalid OHTTP keys"))?; // Parse Bitcoin address and verify network let address = payjoin::bitcoin::Address::from_str(&address)