From 9b5b4cb4a2a4c7e86845839635cb2201bf316bfc Mon Sep 17 00:00:00 2001 From: richardanyalai Date: Tue, 3 Feb 2026 14:43:03 +0700 Subject: [PATCH 1/3] refact: wasm compatibility --- Cargo.lock | 98 ++++++++++++++++++++++++++++++++----------------- Cargo.toml | 27 ++++++++++---- src/lib.rs | 6 ++- src/transfer.rs | 25 ++++++++----- 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 959c796..9c748d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,12 +109,6 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.11.0" @@ -197,7 +191,7 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "common" version = "0.2.0" -source = "git+ssh://git@github.com/DLC-link/canton-lib?tag=v0.2.0#31fa15cbdc1dcbfd23080e6c0858db93f2df5fbe" +source = "git+ssh://git@github.com/DLC-link/canton-lib?branch=refact%2Fwasm#a5716a0d79bf3a961af9cdd27816513f806a11a3" dependencies = [ "canton-api-client", "serde", @@ -932,7 +926,7 @@ dependencies = [ [[package]] name = "keycloak" version = "0.2.0" -source = "git+ssh://git@github.com/DLC-link/canton-lib?tag=v0.2.0#31fa15cbdc1dcbfd23080e6c0858db93f2df5fbe" +source = "git+ssh://git@github.com/DLC-link/canton-lib?branch=refact%2Fwasm#a5716a0d79bf3a961af9cdd27816513f806a11a3" dependencies = [ "base64", "reqwest", @@ -943,18 +937,16 @@ dependencies = [ [[package]] name = "ledger" version = "0.2.0" -source = "git+ssh://git@github.com/DLC-link/canton-lib?tag=v0.2.0#31fa15cbdc1dcbfd23080e6c0858db93f2df5fbe" +source = "git+ssh://git@github.com/DLC-link/canton-lib?branch=refact%2Fwasm#a5716a0d79bf3a961af9cdd27816513f806a11a3" dependencies = [ - "base64", "canton-api-client", "common", "futures-util", "log", - "rand", "reqwest", "serde", "serde_json", - "tokio-tungstenite", + "tokio-tungstenite-wasm", "url", ] @@ -1192,20 +1184,19 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -1213,11 +1204,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.3.4", ] [[package]] @@ -1272,7 +1263,7 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "registry" version = "0.2.0" -source = "git+ssh://git@github.com/DLC-link/canton-lib?tag=v0.2.0#31fa15cbdc1dcbfd23080e6c0858db93f2df5fbe" +source = "git+ssh://git@github.com/DLC-link/canton-lib?branch=refact%2Fwasm#a5716a0d79bf3a961af9cdd27816513f806a11a3" dependencies = [ "common", "reqwest", @@ -1671,18 +1662,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1778,16 +1769,38 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", - "native-tls", + "rustls", + "rustls-pki-types", "tokio", - "tokio-native-tls", + "tokio-rustls", "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-tungstenite-wasm" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e501f4c45ccd240d6ba3d5e191ef4658a7d98ac47091b25a1b0474a5e2aacfd9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "httparse", + "js-sys", + "rustls", + "thiserror", + "tokio", + "tokio-tungstenite", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -1875,21 +1888,20 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.21.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ - "byteorder", "bytes", "data-encoding", "http", "httparse", "log", - "native-tls", "rand", + "rustls", + "rustls-pki-types", "sha1", "thiserror", - "url", "utf-8", ] @@ -2062,6 +2074,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.4", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "windows-core" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index b0053bb..ba7c18c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,25 +8,36 @@ autoexamples = false uninlined_format_args = "allow" [dependencies] -ledger = { git = "ssh://git@github.com/DLC-link/canton-lib", tag = "v0.2.0" } -keycloak = { git = "ssh://git@github.com/DLC-link/canton-lib", tag = "v0.2.0" } -registry = { git = "ssh://git@github.com/DLC-link/canton-lib", tag = "v0.2.0" } -common = { git = "ssh://git@github.com/DLC-link/canton-lib", tag = "v0.2.0" } -tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] } +# Canton-lib dependencies (WASM-compatible after refactoring) +ledger = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "refact/wasm" } +keycloak = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "refact/wasm" } +registry = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "refact/wasm" } +common = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "refact/wasm" } + +# Core dependencies (WASM-compatible) serde_json = "1" chrono = "0.4.42" -uuid = { version = "1.18", features = ["v4"] } -csv = "1.3" serde = { version = "1.0", features = ["derive"] } futures = "0.3" -dotenvy = "0.15" base64 = "0.22" log = "0.4" canton-api-client = "3.3.0-0.1.0" + +# Platform-specific dependencies +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] } +reqwest = { version = "0.12.24", features = ["json"] } +csv = "1.3" +uuid = { version = "1.18", features = ["v4"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1.48.0", features = ["macros", "rt"] } reqwest = { version = "0.12.24", features = ["json"] } +uuid = { version = "1.18", features = ["v4", "js"] } [dev-dependencies] env_logger = "0.11" +dotenvy = "0.15" [[example]] name = "accept_transfers" diff --git a/src/lib.rs b/src/lib.rs index 62f5445..6fc8650 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ +// Core modules available on all targets (including WASM) pub mod accept; pub mod active_contracts; -pub mod batch; pub mod cancel_offers; pub mod consolidate; pub mod distribute; @@ -8,3 +8,7 @@ pub mod mint_redeem; pub mod split; pub mod transfer; pub mod utils; + +// Modules that require file I/O - only available on native targets +#[cfg(not(target_arch = "wasm32"))] +pub mod batch; diff --git a/src/transfer.rs b/src/transfer.rs index 4abf0e5..17ac34e 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -1,3 +1,4 @@ +#[cfg(not(target_arch = "wasm32"))] use crate::active_contracts; use std::collections::HashMap; use std::future::Future; @@ -75,7 +76,7 @@ pub struct TokenState { url: String, username: String, password: String, - expires_at: std::time::SystemTime, + expires_at: chrono::DateTime, } impl TokenState { @@ -102,17 +103,15 @@ impl TokenState { client_id, url, - expires_at: std::time::SystemTime::now() - .checked_sub(std::time::Duration::from_secs( - (token.expires_in - 20) as u64, - )) - .unwrap_or(std::time::SystemTime::now()), + // Set expiry to 20 seconds before actual expiry + expires_at: chrono::Utc::now() + + chrono::Duration::seconds((token.expires_in - 20) as i64), }) } /// Get a fresh token, refreshing if needed (within 1 minute of expiry) pub async fn get_fresh_token(&mut self) -> Result { - let now = std::time::SystemTime::now(); + let now = chrono::Utc::now(); let needs_refresh = now >= self.expires_at; if needs_refresh { @@ -146,8 +145,8 @@ impl TokenState { self.access_token = auth.access_token.clone(); self.refresh_token = auth.refresh_token; // Set expiry to 1 minute before actual expiry - self.expires_at = std::time::SystemTime::now() - + std::time::Duration::from_secs(auth.expires_in as u64 - 60); + self.expires_at = + chrono::Utc::now() + chrono::Duration::seconds(auth.expires_in as i64 - 60); } Ok(self.access_token.clone()) @@ -155,6 +154,8 @@ impl TokenState { } pub async fn submit(mut params: Params) -> Result<(), String> { + // Auto-fetch holdings if not provided (native-only, requires WebSocket) + #[cfg(not(target_arch = "wasm32"))] if params.transfer.input_holding_cids.is_none() { let contracts = active_contracts::get(active_contracts::Params { ledger_host: params.ledger_host.clone(), @@ -170,6 +171,12 @@ pub async fn submit(mut params: Params) -> Result<(), String> { params.transfer.input_holding_cids = Some(input_holding_cids); } + // On WASM, input_holding_cids must be provided + #[cfg(target_arch = "wasm32")] + if params.transfer.input_holding_cids.is_none() { + return Err("input_holding_cids must be provided on WASM target".to_string()); + } + if params.transfer.meta.is_none() { let mut transfer_meta: HashMap = HashMap::new(); transfer_meta.insert( From 93e686f4c1626709179e570e292ba8d2f2b56b59 Mon Sep 17 00:00:00 2001 From: richardanyalai Date: Wed, 11 Feb 2026 13:58:16 +0700 Subject: [PATCH 2/3] refact: unified dependencies --- Cargo.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ba7c18c..5fcf709 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,18 +22,16 @@ futures = "0.3" base64 = "0.22" log = "0.4" canton-api-client = "3.3.0-0.1.0" +reqwest = { version = "0.12.24", features = ["json"] } +uuid = { version = "1.18", features = ["v4", "js"] } # Platform-specific dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] } -reqwest = { version = "0.12.24", features = ["json"] } csv = "1.3" -uuid = { version = "1.18", features = ["v4"] } [target.'cfg(target_arch = "wasm32")'.dependencies] tokio = { version = "1.48.0", features = ["macros", "rt"] } -reqwest = { version = "0.12.24", features = ["json"] } -uuid = { version = "1.18", features = ["v4", "js"] } [dev-dependencies] env_logger = "0.11" From 207c909bd5ec3a0a2152ebeed4359fb5c8a6c5c8 Mon Sep 17 00:00:00 2001 From: richardanyalai Date: Wed, 11 Feb 2026 14:27:05 +0700 Subject: [PATCH 3/3] fix: make batch work on WASM --- Cargo.toml | 2 +- examples/batch_distribute.rs | 5 ++++- src/batch.rs | 22 ++++++---------------- src/lib.rs | 6 +----- src/transfer.rs | 6 ------ 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5fcf709..3d9c2bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,11 @@ log = "0.4" canton-api-client = "3.3.0-0.1.0" reqwest = { version = "0.12.24", features = ["json"] } uuid = { version = "1.18", features = ["v4", "js"] } +csv = "1.3" # Platform-specific dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] } -csv = "1.3" [target.'cfg(target_arch = "wasm32")'.dependencies] tokio = { version = "1.48.0", features = ["macros", "rt"] } diff --git a/examples/batch_distribute.rs b/examples/batch_distribute.rs index a823dc0..be66b42 100644 --- a/examples/batch_distribute.rs +++ b/examples/batch_distribute.rs @@ -26,6 +26,9 @@ async fn main() -> Result<(), String> { )); } + let csv_content = std::fs::read_to_string(&csv_path) + .map_err(|e| format!("Failed to read CSV file '{}': {}", csv_path, e))?; + println!("📦 Batch Distribution"); println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); println!("CSV File: {}", csv_path); @@ -35,7 +38,7 @@ async fn main() -> Result<(), String> { env::var("DECENTRALIZED_PARTY_ID").expect("DECENTRALIZED_PARTY_ID must be set"); let batch_params = cbtc::batch::Params { - csv_path: csv_path.clone(), + csv_content, sender: sender_party.clone(), instrument_id: common::transfer::InstrumentId { admin: decentralized_party.clone(), diff --git a/src/batch.rs b/src/batch.rs index 66d0c58..183f3e2 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -8,7 +8,7 @@ struct CsvRecord { } pub struct Params { - pub csv_path: String, + pub csv_content: String, pub sender: String, pub instrument_id: common::transfer::InstrumentId, pub ledger_host: String, @@ -34,10 +34,9 @@ pub struct Params { /// Each transfer uses the change from the previous transfer, eliminating the /// need for pre-splitting UTXOs. pub async fn submit_from_csv(params: Params) -> Result<(), String> { - // Read CSV file - log::debug!("Reading CSV from: {}", params.csv_path); - let mut reader = csv::Reader::from_path(¶ms.csv_path) - .map_err(|e| format!("Failed to read CSV file: {}", e))?; + // Parse CSV content + log::debug!("Parsing CSV content ({} bytes)", params.csv_content.len()); + let mut reader = csv::Reader::from_reader(params.csv_content.as_bytes()); let mut recipients = Vec::new(); let mut total_amount = 0.0; @@ -112,7 +111,7 @@ mod tests { use super::*; use keycloak::login::password_url; use std::env; - use std::io::Write; + #[tokio::test] async fn test_batch_from_csv() { @@ -121,7 +120,6 @@ mod tests { let receiver = env::var("LIB_TEST_RECEIVER_PARTY_ID").expect("LIB_TEST_RECEIVER_PARTY_ID must be set"); - // Create a temporary CSV file let csv_content = format!( "receiver,amount\n\ {receiver},0.0001\n\ @@ -130,14 +128,9 @@ mod tests { {receiver},0.001\n" ); - let temp_path = "/tmp/test_batch_distribution.csv"; - let mut file = std::fs::File::create(temp_path).expect("Failed to create temp CSV file"); - file.write_all(csv_content.as_bytes()) - .expect("Failed to write CSV content"); - // Run batch distribution (authentication handled internally) let batch_params = Params { - csv_path: temp_path.to_string(), + csv_content, sender: env::var("PARTY_ID").expect("PARTY_ID must be set"), instrument_id: common::transfer::InstrumentId { admin: common::consts::DEVNET_DECENTRALIZED_PARTY_ID.to_string(), @@ -161,8 +154,5 @@ mod tests { }; submit_from_csv(batch_params).await.unwrap(); - - // Clean up - std::fs::remove_file(temp_path).ok(); } } diff --git a/src/lib.rs b/src/lib.rs index 6fc8650..62f5445 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ -// Core modules available on all targets (including WASM) pub mod accept; pub mod active_contracts; +pub mod batch; pub mod cancel_offers; pub mod consolidate; pub mod distribute; @@ -8,7 +8,3 @@ pub mod mint_redeem; pub mod split; pub mod transfer; pub mod utils; - -// Modules that require file I/O - only available on native targets -#[cfg(not(target_arch = "wasm32"))] -pub mod batch; diff --git a/src/transfer.rs b/src/transfer.rs index 17ac34e..dae9ed8 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -171,12 +171,6 @@ pub async fn submit(mut params: Params) -> Result<(), String> { params.transfer.input_holding_cids = Some(input_holding_cids); } - // On WASM, input_holding_cids must be provided - #[cfg(target_arch = "wasm32")] - if params.transfer.input_holding_cids.is_none() { - return Err("input_holding_cids must be provided on WASM target".to_string()); - } - if params.transfer.meta.is_none() { let mut transfer_meta: HashMap = HashMap::new(); transfer_meta.insert(