diff --git a/Cargo.toml b/Cargo.toml index 04e6c87c64..bc7a889be6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -236,6 +236,12 @@ tendermint-rpc = "0.34" # same version as used by cosmrs prost = "0.12" # wasm-related dependencies + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = "0.1.7" gloo-utils = "0.1.7" js-sys = "0.3.63" serde-wasm-bindgen = "0.5.0" diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index 4f2431b45b..7d592e9520 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -17,13 +17,17 @@ use zeroize::Zeroizing; pub mod state; -pub async fn deposit(client: &C, amount: Coin) -> Result +pub async fn deposit( + client: &C, + amount: impl Into, +) -> Result where C: CoconutBandwidthSigningClient + Sync, { let mut rng = OsRng; let signing_key = identity::PrivateKey::new(&mut rng); let encryption_key = encryption::PrivateKey::new(&mut rng); + let amount = amount.into(); let tx_hash = client .deposit( diff --git a/common/client-libs/validator-client/src/nyxd/error.rs b/common/client-libs/validator-client/src/nyxd/error.rs index 905d484b72..0d54f20463 100644 --- a/common/client-libs/validator-client/src/nyxd/error.rs +++ b/common/client-libs/validator-client/src/nyxd/error.rs @@ -44,7 +44,7 @@ pub enum NyxdError { #[error("{0} is not a valid tx hash")] InvalidTxHash(String), - #[error("Tendermint RPC request failed - {0}")] + #[error("Tendermint RPC request failed: {0}")] TendermintErrorRpc(#[from] TendermintRpcError), #[error("tendermint library failure: {0}")] @@ -56,22 +56,22 @@ pub enum NyxdError { #[error("Failed when attempting to deserialize data ({0})")] DeserializationError(String), - #[error("Failed when attempting to encode our protobuf data - {0}")] + #[error("Failed when attempting to encode our protobuf data: {0}")] ProtobufEncodingError(#[from] prost::EncodeError), - #[error("Failed to decode our protobuf data - {0}")] + #[error("Failed to decode our protobuf data: {0}")] ProtobufDecodingError(#[from] prost::DecodeError), - #[error("Account {0} does not exist on the chain")] + #[error("Account '{0}' does not exist on the chain")] NonExistentAccountError(AccountId), - #[error("Failed on json serialization/deserialization - {0}")] + #[error("Failed on json serialization/deserialization: {0}")] SerdeJsonError(#[from] serde_json::Error), - #[error("Account {0} is not a valid account address")] + #[error("Account '{0}' is not a valid account address")] MalformedAccountAddress(String), - #[error("Account {0} has an invalid associated public key")] + #[error("Account '{0}' has an invalid associated public key")] InvalidPublicKey(AccountId), #[error("Queried contract (code_id: {0}) did not have any code information attached")] @@ -86,7 +86,7 @@ pub enum NyxdError { #[error("Block has an invalid height (either negative or larger than i64::MAX")] InvalidHeight, - #[error("Failed to compress provided wasm code - {0}")] + #[error("Failed to compress provided wasm code: {0}")] WasmCompressionError(io::Error), #[error("Logs returned from the validator were malformed")] diff --git a/common/client-libs/validator-client/src/rpc/reqwest.rs b/common/client-libs/validator-client/src/rpc/reqwest.rs index 973fb63376..5c708190f7 100644 --- a/common/client-libs/validator-client/src/rpc/reqwest.rs +++ b/common/client-libs/validator-client/src/rpc/reqwest.rs @@ -109,7 +109,8 @@ trait TendermintRpcErrorMap { impl TendermintRpcErrorMap for reqwest::Error { fn into_rpc_err(self) -> Error { - todo!() + // that's not the best error converion, but it's better than a panic + Error::client_internal(self.to_string()) } } diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index d478759ee7..182ac1325b 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -8,7 +8,6 @@ license.workspace = true [dependencies] async-trait = { workspace = true } - log = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"]} @@ -26,4 +25,4 @@ features = [ "rt-multi-thread", "net", "signal", "fs" ] [build-dependencies] sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } -tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 49c004ece0..50a1609630 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -3,19 +3,6 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; -// #[derive(Clone)] -// pub struct CoconutCredential { -// #[allow(dead_code)] -// pub id: i64, -// pub voucher_value: String, -// pub voucher_info: String, -// pub serial_number: String, -// pub binding_number: String, -// pub signature: String, -// pub epoch_id: String, -// pub consumed: bool, -// } - #[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))] #[derive(Zeroize, ZeroizeOnDrop, Clone)] pub struct StoredIssuedCredential { diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index 3ed6da8de6..136fdcc227 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -17,3 +17,11 @@ schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"]} thiserror = { workspace = true } url = { workspace = true } + +# 'wasm-serde-types' feature +tsify = { workspace = true, features = ["js"], optional = true } +wasm-bindgen = { workspace = true, optional = true } + +[features] +default = [] +wasm-serde-types = ["tsify", "wasm-bindgen"] \ No newline at end of file diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 55037c50ee..3ad502ebe3 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -11,39 +11,96 @@ use std::{ ffi::OsStr, ops::Not, }; -use url::Url; +pub use url::{ParseError as UrlParseError, Url}; + +#[cfg(feature = "wasm-serde-types")] +use tsify::Tsify; + +#[cfg(feature = "wasm-serde-types")] +use wasm_bindgen::prelude::wasm_bindgen; pub mod mainnet; pub mod var_names; #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))] +#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct ChainDetails { + #[serde(alias = "bech32_account_prefix")] pub bech32_account_prefix: String, + + #[serde(alias = "mix_denom")] pub mix_denom: DenomDetailsOwned, + + #[serde(alias = "stake_denom")] pub stake_denom: DenomDetailsOwned, } #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))] +#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct NymContracts { + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "mixnet_contract_address")] pub mixnet_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "vesting_contract_address")] pub vesting_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "coconut_bandwidth_contract_address")] pub coconut_bandwidth_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "group_contract_address")] pub group_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "multisig_contract_address")] pub multisig_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "coconut_dkg_contract_address")] pub coconut_dkg_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "ephemera_contract_address")] pub ephemera_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "service_provider_directory_contract_address")] pub service_provider_directory_contract_address: Option, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "name_service_contract_address")] pub name_service_contract_address: Option, } // I wanted to use the simpler `NetworkDetails` name, but there's a clash // with `NetworkDetails` defined in all.rs... #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))] +#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct NymNetworkDetails { + #[serde(alias = "network_name")] pub network_name: String, + + #[serde(alias = "chain_details")] pub chain_details: ChainDetails, + pub endpoints: Vec, + pub contracts: NymContracts, + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "explorer_api")] pub explorer_api: Option, } @@ -316,10 +373,17 @@ impl DenomDetails { } #[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq, JsonSchema)] +#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))] +#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct DenomDetailsOwned { pub base: String, + pub display: String, + // i.e. display_amount * 10^display_exponent = base_amount + #[serde(alias = "display_exponent")] pub display_exponent: u32, } @@ -344,14 +408,24 @@ impl DenomDetailsOwned { } #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))] +#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +#[non_exhaustive] pub struct ValidatorDetails { // it is assumed those values are always valid since they're being provided in our defaults file + #[serde(alias = "nyxd_url")] pub nyxd_url: String, - // + + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "websocket_url")] pub websocket_url: Option, // Right now api_url is optional as we are not running the api reliably on all validators // however, later on it should be a mandatory field + #[cfg_attr(feature = "wasm-serde-types", tsify(optional))] + #[serde(alias = "api_url")] pub api_url: Option, // TODO: I'd argue this one should also have a field like `gas_price` since its a validator-specific setting } @@ -373,16 +447,26 @@ impl ValidatorDetails { } } + pub fn try_nyxd_url(&self) -> Result { + self.nyxd_url.parse() + } + pub fn nyxd_url(&self) -> Url { - self.nyxd_url - .parse() + self.try_nyxd_url() .expect("the provided nyxd url is invalid!") } + pub fn try_api_url(&self) -> Option> { + self.api_url.as_ref().map(|url| url.parse()) + } + pub fn api_url(&self) -> Option { - self.api_url - .as_ref() - .map(|url| url.parse().expect("the provided api url is invalid!")) + self.try_api_url() + .map(|url| url.expect("the provided api url is invalid!")) + } + + pub fn try_websocket_url(&self) -> Option> { + self.websocket_url.as_ref().map(|url| url.parse()) } pub fn websocket_url(&self) -> Option { diff --git a/common/wasm/client-core/Cargo.toml b/common/wasm/client-core/Cargo.toml index 5e6a701883..0387c139a0 100644 --- a/common/wasm/client-core/Cargo.toml +++ b/common/wasm/client-core/Cargo.toml @@ -37,11 +37,5 @@ wasm-utils = { path = "../utils" } wasm-storage = { path = "../storage" } -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = { version = "0.1", optional = true } - [features] -default = ["console_error_panic_hook"] \ No newline at end of file +default = ["wasm-utils/console_error_panic_hook"] \ No newline at end of file diff --git a/common/wasm/utils/Cargo.toml b/common/wasm/utils/Cargo.toml index ee3536b260..43f7bada71 100644 --- a/common/wasm/utils/Cargo.toml +++ b/common/wasm/utils/Cargo.toml @@ -17,6 +17,12 @@ gloo-utils = { workspace = true } gloo-net = { version = "0.3.1", features = ["websocket"], optional = true } #gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true } +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { workspace = true, optional = true } + # we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums [dependencies.tungstenite] workspace = true @@ -28,7 +34,7 @@ workspace = true optional = true [features] -default = ["sleep"] +default = ["sleep", "console_error_panic_hook"] sleep = ["web-sys", "web-sys/Window"] websocket = [ "getrandom", diff --git a/nym-browser-extension/storage/Cargo.toml b/nym-browser-extension/storage/Cargo.toml index 68ed96e26b..2ffbcdcda9 100644 --- a/nym-browser-extension/storage/Cargo.toml +++ b/nym-browser-extension/storage/Cargo.toml @@ -19,7 +19,7 @@ wasm-bindgen = { workspace = true } wasm-bindgen-futures = { workspace = true } zeroize = { workspace = true } -console_error_panic_hook = { version = "0.1", optional = true } +console_error_panic_hook = { workspace = true , optional = true } wasm-utils = { path = "../../common/wasm/utils" } wasm-storage = { path = "../../common/wasm/storage" } diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index be63448ae5..845f5c612d 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -962,6 +962,16 @@ dependencies = [ "crossbeam-utils", ] +[[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 = "const-oid" version = "0.9.5" @@ -7988,6 +7998,7 @@ dependencies = [ name = "wasm-utils" version = "0.1.0" dependencies = [ + "console_error_panic_hook", "futures", "getrandom 0.2.10", "gloo-net", diff --git a/wasm/credentials/Cargo.toml b/wasm/credentials/Cargo.toml index 021f92771a..e9b9d1a7f9 100644 --- a/wasm/credentials/Cargo.toml +++ b/wasm/credentials/Cargo.toml @@ -3,7 +3,7 @@ name = "nym-credential-client-wasm" authors = [] version = "1.2.0-rc.9" edition = "2021" -keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"] +keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"] license = "Apache-2.0" repository = "https://github.com/nymtech/nym" description = "A webassembly client which can be used to issue Coconut credentials." @@ -13,25 +13,25 @@ rust-version = "1.56" crate-type = ["cdylib", "rlib"] [dependencies] -anyhow = "1.0" -futures = { workspace = true } +bip39 = { workspace = true } +#futures = { workspace = true } js-sys = { workspace = true } -rand = { version = "0.7.3", features = ["wasm-bindgen"] } +#rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { workspace = true, features = ["derive"] } -serde_json = "1.0" +#serde_json = { workspace = true } serde-wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true } wasm-bindgen-futures = { workspace = true } thiserror = { workspace = true } tsify = { workspace = true, features = ["js"] } +zeroize = { workspace = true } -wasm-client-core = { path = "../../common/wasm/client-core" } wasm-utils = { path = "../../common/wasm/utils" } nym-credentials = { path = "../../common/credentials" } +nym-credential-storage = { path = "../../common/credential-storage" } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] } -nym-crypto = { path = "../../common/crypto" } +nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false } nym-coconut-interface = { path = "../../common/coconut-interface" } nym-network-defaults = { path = "../../common/network-defaults" } diff --git a/wasm/credentials/Makefile b/wasm/credentials/Makefile index 86e651762e..3f49bb49ee 100644 --- a/wasm/credentials/Makefile +++ b/wasm/credentials/Makefile @@ -1,3 +1,7 @@ build: wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/credentials - wasm-opt -Oz -o ../../dist/wasm/credentials/nym_node_tester_wasm_bg.wasm ../../dist/wasm/credentials/nym_node_tester_wasm_bg.wasm + wasm-opt -Oz -o ../../dist/wasm/credentials/nym_credential_client_wasm_bg.wasm ../../dist/wasm/credentials/nym_credential_client_wasm_bg.wasm + +# make my life easier so I wouldn't need to deal with any bundlers. sorry @MS : ) +build-debug-dev: + wasm-pack build --scope nymproject --target no-modules \ No newline at end of file diff --git a/wasm/credentials/internal-dev/index.html b/wasm/credentials/internal-dev/index.html index 0f4992f949..bf70d2046d 100644 --- a/wasm/credentials/internal-dev/index.html +++ b/wasm/credentials/internal-dev/index.html @@ -9,8 +9,24 @@ + +

Yet another coconut demo

+
+ + + +
+
+ + + + + +
+ +

- Create here the demo +

diff --git a/wasm/credentials/internal-dev/index.js b/wasm/credentials/internal-dev/index.js index 0ff5c3a191..1d0200d9e4 100644 --- a/wasm/credentials/internal-dev/index.js +++ b/wasm/credentials/internal-dev/index.js @@ -21,59 +21,29 @@ class WebWorkerClient { this.worker.onmessage = (ev) => { if (ev.data && ev.data.kind) { switch (ev.data.kind) { - case 'Ready': - const {selfAddress} = ev.data.args; - displaySenderAddress(selfAddress); - break; - case 'ReceiveMessage': - const {message, senderTag, isTestPacket } = ev.data.args; - displayReceived(message, senderTag, isTestPacket); - break; - case 'DisplayString': - const { rawString } = ev.data.args; - displayReceivedRawString(rawString) - break; - case 'DisableMagicTestButton': - const magicButton = document.querySelector('#magic-button'); - magicButton.setAttribute('disabled', "true") - break; - case 'DisplayTesterResults': - const {score, sentPackets, receivedPackets, receivedAcks, duplicatePackets, duplicateAcks} = ev.data.args; - const resultText = `Test score: ${score}. Sent ${sentPackets} packets. Received ${receivedPackets} packets and ${receivedAcks} acks back. We also got ${duplicatePackets} duplicate packets and ${duplicateAcks} duplicate acks.` - displayReceivedRawString(resultText) + case 'ReceivedCredential': + const { credential } = ev.data.args; + displayCredential(credential) break; } } }; } - sendMessage = (message, recipient) => { + getCredential = (amount, mnemonic) => { if (!this.worker) { - console.error('Could not send message because worker does not exist'); + console.error('Could not get credential because worker does not exist'); return; } this.worker.postMessage({ - kind: 'SendMessage', + kind: 'GetCredential', args: { - message, recipient, + amount, + mnemonic }, }); }; - - sendMagicPayload = (mixnodeIdentity) => { - if (!this.worker) { - console.error('Could not send message because worker does not exist'); - return; - } - - this.worker.postMessage({ - kind: 'MagicPayload', - args: { - mixnodeIdentity, - }, - }); - } } let client = null; @@ -81,93 +51,35 @@ let client = null; async function main() { client = new WebWorkerClient(); - const sendButton = document.querySelector('#send-button'); - sendButton.onclick = function () { - sendMessageTo(); + const coconutButton = document.querySelector('#coconut-button'); + coconutButton.onclick = function () { + getCredential(); }; - - const magicButton = document.querySelector('#magic-button'); - magicButton.onclick = function () { - sendMagicPayload(); - } } -/** - * Create a Sphinx packet and send it to the mixnet through the gateway node. - * - * Message and recipient are taken from the values in the user interface. - * - */ -async function sendMessageTo() { - const message = document.getElementById('message').value; - const recipient = document.getElementById('recipient').value; +async function getCredential() { + const amount = document.getElementById('credential-amount').value; + const mnemonic = document.getElementById('mnemonic').value; - await client.sendMessage(message, recipient); - displaySend(message); + await client.getCredential(amount, mnemonic); } -async function sendMagicPayload() { - const payload = document.getElementById('magic_payload').value; - await client.sendMagicPayload(payload) - displaySend(`clicked the button and the payload is: ${payload}...`); -} -/** - * Display messages that have been sent up the websocket. Colours them blue. - * - * @param {string} message - */ -function displaySend(message) { +function displayCredential(credential) { + console.log("got credential", credential) + let timestamp = new Date().toISOString().substr(11, 12); - let sendDiv = document.createElement('div'); + let credentialDiv = document.createElement('div'); let paragraph = document.createElement('p'); paragraph.setAttribute('style', 'color: blue'); - let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message); + let paragraphContent = document.createTextNode(timestamp + ' 🥥🥥🥥 >>> ' + JSON.stringify(credential)); paragraph.appendChild(paragraphContent); - sendDiv.appendChild(paragraph); - document.getElementById('output').appendChild(sendDiv); + credentialDiv.appendChild(paragraph); + document.getElementById('output').appendChild(credentialDiv); } -/** - * Display received text messages in the browser. Colour them green. - * - * @param {Uint8Array} raw - */ -function displayReceived(raw, sender_tag, isTestPacket) { - let content = new TextDecoder().decode(raw); - if (sender_tag !== undefined) { - console.log("this message also contained some surbs from", sender_tag) - } - - if (isTestPacket) { - const decoded = JSON.parse(content) - content = `Received packet ${decoded.msg_id} / ${decoded.total_msgs} for node ${decoded.encoded_node_identity} (test: ${decoded.test_id})` - } - - displayReceivedRawString(content) -} - -function displayReceivedRawString(raw) { - let timestamp = new Date().toISOString().substr(11, 12); - let receivedDiv = document.createElement('div'); - let paragraph = document.createElement('p'); - paragraph.setAttribute('style', 'color: green'); - let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + raw); - paragraph.appendChild(paragraphContent); - receivedDiv.appendChild(paragraph); - document.getElementById('output').appendChild(receivedDiv); -} - -/** - * Display the nymClient's sender address in the user interface - * - * @param {String} address - */ -function displaySenderAddress(address) { - document.getElementById('sender').value = address; -} main(); diff --git a/wasm/credentials/internal-dev/package.json b/wasm/credentials/internal-dev/package.json index b3e8060f42..0da80a59eb 100644 --- a/wasm/credentials/internal-dev/package.json +++ b/wasm/credentials/internal-dev/package.json @@ -35,6 +35,6 @@ "webpack-dev-server": "^4.7.4" }, "dependencies": { - "@nymproject/nym_node_tester_wasm": "file:../../../dist/wasm/credentials" + "@nymproject/nym-credential-client-wasm": "file:../pkg" } } diff --git a/wasm/credentials/internal-dev/webpack.config.js b/wasm/credentials/internal-dev/webpack.config.js index 7940cc8cc2..bc1242190d 100644 --- a/wasm/credentials/internal-dev/webpack.config.js +++ b/wasm/credentials/internal-dev/webpack.config.js @@ -22,7 +22,7 @@ module.exports = { patterns: [ 'index.html', { - from: '../../../dist/wasm/credentials/*.(js|wasm)', + from: '../pkg/*.(js|wasm)', to: '[name][ext]', }, ], diff --git a/wasm/credentials/internal-dev/worker.js b/wasm/credentials/internal-dev/worker.js index d7827a31e1..9e4831d5bf 100644 --- a/wasm/credentials/internal-dev/worker.js +++ b/wasm/credentials/internal-dev/worker.js @@ -12,120 +12,54 @@ // See the License for the specific language governing permissions and // limitations under the License. -const RUST_WASM_URL = "nym_credentials_wasm_bg.wasm" +const RUST_WASM_URL = "nym_credential_client_wasm_bg.wasm" -importScripts('nym_credentials_wasm_bg.js'); +importScripts('nym_credential_client_wasm.js'); console.log('Initializing worker'); // wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts` const { - default_debug, - no_cover_debug, - NymNodeTester, - set_panic_hook, - currentNetworkTopology, + acquireCredential, } = wasm_bindgen; -let client = null; - -function dummyTopology() { - const l1Mixnode = { - mixId: 1, - owner: 'n1lftjhnl35cjsfd533zhgrwrspx6qmumd8vjgp9', - host: '80.85.86.75', - mixPort: 1789, - identityKey: '91mNjhJSBkJ9Lb6f1iuYMDQPLiX3kAv6paSUCWjGRwQz', - sphinxKey: 'DmfN1mL1T95nPXvLK44AQKCpW1pStHNQCi6Fgpz5dxDV', - layer: 1, - version: '1.1.20', - }; - const l2Mixnode = { - mixId: 2, - owner: 'n18ztkyh20gwzrel0e5m4sahd358fq9p4skwa7d3', - host: '139.162.199.75', - mixPort: 1789, - identityKey: 'BkLhuKQNyPS19sHZ3HHKCTKwK7hCU6XiFLndyZZHiB7s', - sphinxKey: '7KGC97tJRhJZKhDqFcsp4Vu715VVxizuD7BktnzuSmZC', - layer: 2, - version: '1.1.20', - }; - const l3Mixnode = { - mixId: 3, - owner: 'n1njq8h4nndp7ngays5el2rdp22hq67lwqcaq3ph', - host: '139.162.244.139', - identityKey: 'EPja9Kv8JtPHsFbzPdBQierMu5GmQy5roE5njyD6dmND', - sphinxKey: 'HWpsZChDrtEH8XNscW3qJMRzdCfUD8N8DmMcKqFv7tcf', - layer: 3, - }; - - const gateway = { - owner: 'n1d9lclqnfddgg57xe5p0fw4ng54m9f95hal5tlq', - host: '85.159.211.99', - mixPort: 1789, - clientsPort: 9000, - identityKey: '6pXQcG1Jt9hxBzMgTbQL5Y58z6mu4KXVRbA1idmibwsw', - sphinxKey: 'GSdqV7GFSwHWQrVV13pNLMeafTLDVFKBKVPxuhdGrpR3', - version: '1.1.19', - }; - - const mixnodes = new Map(); - mixnodes.set(1, [l1Mixnode]); - mixnodes.set(2, [l2Mixnode]); - mixnodes.set(3, [l3Mixnode]); - - const gateways = [gateway]; - - return { - mixnodes, gateways - } -} - - -function printAndDisplayTestResult(result) { - result.log_details(); - - self.postMessage({ - kind: 'DisplayTesterResults', - args: { - score: result.score(), - sentPackets: result.sent_packets, - receivedPackets: result.received_packets, - receivedAcks: result.received_acks, - duplicatePackets: result.duplicate_packets, - duplicateAcks: result.duplicate_acks, - }, - }); -} - -async function testWithNymCredentials() { - const preferredGateway = "6pXQcG1Jt9hxBzMgTbQL5Y58z6mu4KXVRbA1idmibwsw"; - const nymApi = 'https://qa-nym-api.qa.nymte.ch/api'; - - // A) construct with hardcoded topology - const topology = dummyTopology() - - // optional arguments: id, gateway - // mandatory (one of) arguments: topology, nymApi - const nodeTester = await new NymNodeTester({ id: "foomp", topology: topology }); - - // B) first get topology directly from nym-api - // const topology = await currentNetworkTopology(nymApi) - // const nodeTester = await new NymNodeTester({topology}); - - // C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers) - // const validator = 'https://qa-nym-api.qa.nymte.ch/api'; - // const nodeTester = await new NymNodeTester({nymApi}); - +async function testGetCredential() { + // const preferredGateway = "6pXQcG1Jt9hxBzMgTbQL5Y58z6mu4KXVRbA1idmibwsw"; + // const nymApi = 'https://qa-nym-api.qa.nymte.ch/api'; + // + // // A) construct with hardcoded topology + // const topology = dummyTopology() + // + // // optional arguments: id, gateway + // // mandatory (one of) arguments: topology, nymApi + // const nodeTester = await new NymNodeTester({ id: "foomp", topology: topology }); + // + // // B) first get topology directly from nym-api + // // const topology = await currentNetworkTopology(nymApi) + // // const nodeTester = await new NymNodeTester({topology}); + // + // // C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers) + // // const validator = 'https://qa-nym-api.qa.nymte.ch/api'; + // // const nodeTester = await new NymNodeTester({nymApi}); + // self.onmessage = async event => { if (event.data && event.data.kind) { switch (event.data.kind) { - case 'MagicPayload': { - const {mixnodeIdentity} = event.data.args; - console.log("starting node test..."); + case 'GetCredential': { + const { amount, mnemonic } = event.data.args; - let result = await nodeTester.test_node(mixnodeIdentity); - printAndDisplayTestResult(result) + // TODO: this should just use cosmjs' coin + let coin = `${amount}unym` + console.log(`getting credential for ${coin}`); + + let credential = await acquireCredential(mnemonic, coin, { useSandbox: true }) + + self.postMessage({ + kind : 'ReceivedCredential', + args: { + credential + } + }) } } } @@ -139,11 +73,8 @@ async function main() { await wasm_bindgen(RUST_WASM_URL); console.log('Loaded RUST WASM'); - // sets up better stack traces in case of in-rust panics - set_panic_hook(); - // // run test on simplified and dedicated tester: - await testWithTester(); + await testGetCredential(); // console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN END") } diff --git a/wasm/credentials/internal-dev/yarn.lock b/wasm/credentials/internal-dev/yarn.lock index df787a4a65..cd7cdf3aa8 100644 --- a/wasm/credentials/internal-dev/yarn.lock +++ b/wasm/credentials/internal-dev/yarn.lock @@ -73,8 +73,8 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nymproject/nym_node_tester_wasm@file:../../../dist/wasm/node-tester": - version "1.0.0" +"@nymproject/nym-credential-client-wasm@file:../pkg": + version "1.2.0-rc.9" "@types/body-parser@*": version "1.19.2" @@ -445,11 +445,6 @@ array-flatten@^2.1.2: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-union@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" - integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -642,14 +637,14 @@ cookie@0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -copy-webpack-plugin@^10.2.4: - version "10.2.4" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" - integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg== +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== dependencies: - fast-glob "^3.2.7" + fast-glob "^3.2.11" glob-parent "^6.0.1" - globby "^12.0.2" + globby "^13.1.1" normalize-path "^3.0.0" schema-utils "^4.0.0" serialize-javascript "^6.0.0" @@ -873,10 +868,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.7: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== +fast-glob@^3.2.11, fast-glob@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1016,15 +1011,14 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globby@^12.0.2: - version "12.2.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22" - integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA== +globby@^13.1.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== dependencies: - array-union "^3.0.1" dir-glob "^3.0.1" - fast-glob "^3.2.7" - ignore "^5.1.9" + fast-glob "^3.3.0" + ignore "^5.2.4" merge2 "^1.4.1" slash "^4.0.0" @@ -1138,7 +1132,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^5.1.9: +ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== diff --git a/wasm/credentials/src/credential.rs b/wasm/credentials/src/credential.rs new file mode 100644 index 0000000000..e74926618a --- /dev/null +++ b/wasm/credentials/src/credential.rs @@ -0,0 +1,121 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::WasmCredentialClientError; +use crate::opts::CredentialClientOpts; +use js_sys::Promise; +use nym_credential_storage::ephemeral_storage::EphemeralCredentialStorage; +use nym_credential_storage::models::CoconutCredential; +use nym_credential_storage::storage::Storage; +use nym_network_defaults::NymNetworkDetails; +use nym_validator_client::nyxd::{Config, CosmWasmCoin}; +use nym_validator_client::DirectSigningReqwestRpcNyxdClient; +use serde::{Deserialize, Serialize}; +use tsify::Tsify; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; +use wasm_utils::console_log; +use wasm_utils::error::PromisableResult; + +#[wasm_bindgen(js_name = acquireCredential)] +pub fn acquire_credential(mnemonic: String, amount: String, opts: CredentialClientOpts) -> Promise { + future_to_promise(async move { + acquire_credential_async(mnemonic, amount, opts) + .await + .map(|credential| { + serde_wasm_bindgen::to_value(&credential).expect("this serialization can't fail") + }) + .into_promise_result() + }) +} + +async fn acquire_credential_async( + mnemonic: String, + amount: String, + opts: CredentialClientOpts, +) -> Result { + // start by parsing mnemonic so that we could immediately move it into a Zeroizing wrapper + let mnemonic = crate::helpers::parse_mnemonic(mnemonic)?; + + // why are we parsing into CosmWasmCoin and not "our" Coin? + // simple. because it has the nicest 'FromStr' impl + let amount: CosmWasmCoin = + amount + .parse() + .map_err(|source| WasmCredentialClientError::MalformedCoin { + source: Box::new(source), + })?; + + if amount.amount.is_zero() { + return Err(WasmCredentialClientError::ZeroCoinValue); + } + + let network = match opts.network_details { + Some(specified) => specified, + None => { + if let Some(true) = opts.use_sandbox { + crate::helpers::minimal_coconut_sandbox() + } else { + NymNetworkDetails::new_mainnet() + } + } + }; + + let config = Config::try_from_nym_network_details(&network)?; + + // just get the first nyxd endpoint + let nyxd_endpoint = network + .endpoints + .get(0) + .ok_or(WasmCredentialClientError::NoNyxdEndpoints)? + .try_nyxd_url()?; + + let client = DirectSigningReqwestRpcNyxdClient::connect_reqwest_with_mnemonic( + config, + nyxd_endpoint, + mnemonic, + ); + + console_log!("starting the deposit..."); + let deposit_state = nym_bandwidth_controller::acquire::deposit(&client, amount).await?; + console_log!("obtained voucher: {:#?}", deposit_state.voucher); + + // TODO: use proper persistent storage here. probably indexeddb like we have for our 'normal' wasm client + let ephemeral_storage = EphemeralCredentialStorage::default(); + + // store credential in the ephemeral storage... + nym_bandwidth_controller::acquire::get_credential(&deposit_state, &client, &ephemeral_storage) + .await?; + + // and immediately get it out! + let cred = ephemeral_storage.get_next_coconut_credential().await?; + + Ok(cred.into()) +} + +#[derive(Tsify, Debug, Clone, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[serde(rename_all = "camelCase")] +// we could have implemented all ts traits on normal CoconutCredential, +// but it felt very awkward to import those crates in there; plus the underlying type might change +pub struct WasmCoconutCredential { + pub voucher_value: String, + pub voucher_info: String, + pub serial_number: String, + pub binding_number: String, + pub signature: String, + pub epoch_id: String, +} + +impl From for WasmCoconutCredential { + fn from(value: CoconutCredential) -> Self { + WasmCoconutCredential { + voucher_value: value.voucher_value, + voucher_info: value.voucher_info, + serial_number: value.serial_number, + binding_number: value.binding_number, + signature: value.signature, + epoch_id: value.epoch_id, + } + } +} diff --git a/wasm/credentials/src/error.rs b/wasm/credentials/src/error.rs new file mode 100644 index 0000000000..24e54a73bb --- /dev/null +++ b/wasm/credentials/src/error.rs @@ -0,0 +1,61 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use js_sys::Promise; +use nym_bandwidth_controller::error::BandwidthControllerError; +use nym_network_defaults::UrlParseError; +use nym_validator_client::nyxd::error::NyxdError; +use thiserror::Error; +use wasm_utils::wasm_error; + +#[derive(Debug, Error)] +pub enum WasmCredentialClientError { + #[error(transparent)] + BandwidthControllerError { + #[from] + source: BandwidthControllerError, + }, + + #[error("the passed credential value had a value of zero")] + ZeroCoinValue, + + #[error("failed to use credential storage: {source}")] + StorageError { + #[from] + source: nym_credential_storage::error::StorageError, + }, + + #[error(transparent)] + NyxdFailure { + #[from] + source: NyxdError, + }, + + #[error("no nyxd endpoints have been provided - we can't interact with the chain")] + NoNyxdEndpoints, + + #[error("the provided nyxd endpoint is malformed: {source}")] + MalformedNyxdEndpoint { + #[from] + source: UrlParseError, + }, + + // #[error("The provided deposit value was malformed: {source}")] + // MalformedCoin { source: serde_wasm_bindgen::Error }, + #[error("The provided deposit value was malformed: {source}")] + // annoyingly cosmwasm hasn't exposed CoinFromStrError directly + // so we have to rely on the dynamic dispatch here + MalformedCoin { source: Box }, + + // #[error("Coin parse error")] + // CoinParseError, + // #[error("State error")] + // StateError, + #[error("The provided mnemonic was malformed: {source}")] + MalformedMnemonic { + #[from] + source: bip39::Error, + }, +} + +wasm_error!(WasmCredentialClientError); diff --git a/wasm/credentials/src/helpers.rs b/wasm/credentials/src/helpers.rs new file mode 100644 index 0000000000..8d9e137131 --- /dev/null +++ b/wasm/credentials/src/helpers.rs @@ -0,0 +1,38 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::WasmCredentialClientError; +use nym_network_defaults::{NymContracts, NymNetworkDetails, ValidatorDetails}; +use zeroize::Zeroizing; + +pub(crate) fn parse_mnemonic(raw: String) -> Result { + // make sure that whatever happens, the raw value gets zeroized + let wrapped = Zeroizing::new(raw); + Ok(bip39::Mnemonic::parse(&*wrapped)?) +} + +pub(crate) fn minimal_coconut_sandbox() -> NymNetworkDetails { + // we can piggyback on mainnet defaults for certain things, + // since sandbox uses the same network name, denoms, etc. + let default_mainnet = NymNetworkDetails::new_mainnet(); + + NymNetworkDetails { + network_name: default_mainnet.network_name, + chain_details: default_mainnet.chain_details, + endpoints: vec![ValidatorDetails::new( + "https://sandbox-validator1.nymtech.net", + None, + )], + contracts: NymContracts { + coconut_bandwidth_contract_address: Some( + "n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l".into(), + ), + coconut_dkg_contract_address: Some( + "n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a".into(), + ), + // we don't need other contracts for getting credential + ..Default::default() + }, + explorer_api: None, + } +} diff --git a/wasm/credentials/src/lib.rs b/wasm/credentials/src/lib.rs index 20dc6dbe6c..f528f49879 100644 --- a/wasm/credentials/src/lib.rs +++ b/wasm/credentials/src/lib.rs @@ -1,114 +1,22 @@ -use std::str::FromStr; +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -use js_sys::Promise; -use thiserror::Error; -use wasm_bindgen::prelude::wasm_bindgen; +#[cfg(target_arch = "wasm32")] +mod credential; +#[cfg(target_arch = "wasm32")] +mod error; +#[cfg(target_arch = "wasm32")] +mod helpers; +#[cfg(target_arch = "wasm32")] +mod opts; -use nym_bandwidth_controller::acquire::deposit; -use nym_bandwidth_controller::acquire::state::State; -use nym_bandwidth_controller::error::BandwidthControllerError; -use nym_coconut_interface::Signature; -use nym_credentials::coconut::utils::obtain_aggregate_signature; -use nym_network_defaults::NymNetworkDetails; -use nym_validator_client::coconut::all_coconut_api_clients; -use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient; -use nym_validator_client::nyxd::contract_traits::DkgQueryClient; -use nym_validator_client::nyxd::error::NyxdError; -use nym_validator_client::nyxd::{Coin, CosmWasmCoin}; -use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; -use wasm_utils::wasm_error; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; -#[wasm_bindgen] -struct WasmCredentialClient { - state: Option, - client: DirectSigningHttpRpcNyxdClient, +#[wasm_bindgen(start)] +#[cfg(target_arch = "wasm32")] +pub fn main() { + wasm_utils::console_log!("[rust main]: rust module loaded"); + wasm_utils::console_log!("[rust main]: setting panic hook"); + wasm_utils::set_panic_hook(); } - -#[wasm_bindgen] -impl WasmCredentialClient { - #[wasm_bindgen(constructor)] - pub fn new( - // network_details: NymNetworkDetails, - mnemonic: String, - ) -> Result { - let network_details = NymNetworkDetails::new_mainnet(); - let client = WasmCredentialClient::create_client(network_details, mnemonic)?; - Ok(WasmCredentialClient { - state: None, - client, - }) - } - - #[wasm_bindgen] - pub async fn deposit( - &mut self, - amount_with_denom: String, - ) -> Result<(), WasmCredentialClientError> { - match CosmWasmCoin::from_str(&amount_with_denom) { - Ok(coin) => { - let state = deposit(&self.client, Coin::from(coin)).await?; - self.state = Some(state); - Ok(()) - } - Err(_e) => Err(WasmCredentialClientError::CoinParseError), - } - } - - #[wasm_bindgen] - pub async fn get_credential(self) -> Result { - let epoch_id = self.client.get_current_epoch().await?.epoch_id; - let threshold = self - .client - .get_current_epoch_threshold() - .await? - .ok_or(BandwidthControllerError::NoThreshold)?; - - let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?; - - match self.state { - Some(state) => { - let signature = obtain_aggregate_signature( - &self.state.params, - &self.state.voucher, - &coconut_api_clients, - threshold, - ) - .await?; - - Ok(signature) - } - None => Err(WasmCredentialClientError::StateError), - } - } - - fn create_client( - network_details: NymNetworkDetails, - mnemonic: String, - ) -> Result { - let nyxd_url = network_details.endpoints[0].nyxd_url.as_str(); - let config = nyxd::Config::try_from_nym_network_details(&network_details)?; - - let client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( - config, - nyxd_url, - mnemonic.parse()?, - )?; - - Ok(client) - } -} - -#[derive(Debug, Error)] -pub enum WasmCredentialClientError { - #[error(transparent)] - BandwidthControllerError { - #[from] - source: BandwidthControllerError, - }, - #[error("Coin parse error")] - CoinParseError, - #[error("State error")] - StateError, -} - -wasm_error!(WasmCredentialClientError); diff --git a/wasm/credentials/src/opts.rs b/wasm/credentials/src/opts.rs new file mode 100644 index 0000000000..599b15b634 --- /dev/null +++ b/wasm/credentials/src/opts.rs @@ -0,0 +1,17 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_network_defaults::NymNetworkDetails; +use serde::{Deserialize, Serialize}; +use tsify::Tsify; + +#[derive(Tsify, Debug, Clone, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[serde(rename_all = "camelCase")] +pub struct CredentialClientOpts { + #[tsify(optional)] + pub network_details: Option, + + #[tsify(optional)] + pub use_sandbox: Option, +}