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..3d9c2bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,25 +8,34 @@ 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" 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"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1.48.0", features = ["macros", "rt"] } [dev-dependencies] env_logger = "0.11" +dotenvy = "0.15" [[example]] name = "accept_transfers" 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/transfer.rs b/src/transfer.rs index 4abf0e5..dae9ed8 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(),