implemented acquireCredential method in the wasm credential client
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -17,13 +17,17 @@ use zeroize::Zeroizing;
|
||||
|
||||
pub mod state;
|
||||
|
||||
pub async fn deposit<C>(client: &C, amount: Coin) -> Result<State, BandwidthControllerError>
|
||||
pub async fn deposit<C>(
|
||||
client: &C,
|
||||
amount: impl Into<Coin>,
|
||||
) -> Result<State, BandwidthControllerError>
|
||||
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(
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"]
|
||||
@@ -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<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "vesting_contract_address")]
|
||||
pub vesting_contract_address: Option<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "coconut_bandwidth_contract_address")]
|
||||
pub coconut_bandwidth_contract_address: Option<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "group_contract_address")]
|
||||
pub group_contract_address: Option<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "multisig_contract_address")]
|
||||
pub multisig_contract_address: Option<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "coconut_dkg_contract_address")]
|
||||
pub coconut_dkg_contract_address: Option<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "ephemera_contract_address")]
|
||||
pub ephemera_contract_address: Option<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "service_provider_directory_contract_address")]
|
||||
pub service_provider_directory_contract_address: Option<String>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "name_service_contract_address")]
|
||||
pub name_service_contract_address: Option<String>,
|
||||
}
|
||||
|
||||
// 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<ValidatorDetails>,
|
||||
|
||||
pub contracts: NymContracts,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "explorer_api")]
|
||||
pub explorer_api: Option<String>,
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
|
||||
// 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<String>,
|
||||
// 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<Url, url::ParseError> {
|
||||
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<Result<Url, url::ParseError>> {
|
||||
self.api_url.as_ref().map(|url| url.parse())
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> Option<Url> {
|
||||
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<Result<Url, url::ParseError>> {
|
||||
self.websocket_url.as_ref().map(|url| url.parse())
|
||||
}
|
||||
|
||||
pub fn websocket_url(&self) -> Option<Url> {
|
||||
|
||||
@@ -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"]
|
||||
default = ["wasm-utils/console_error_panic_hook"]
|
||||
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Generated
+11
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -9,8 +9,24 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1> Yet another coconut demo </h1>
|
||||
<div>
|
||||
<label>mnemonic: </label>
|
||||
<input type="text" size = "120" id="mnemonic" value="...">
|
||||
|
||||
</br>
|
||||
</br>
|
||||
|
||||
<label>amount (in <b>unym</b>): </label>
|
||||
<input type="number" size = "60" id="credential-amount" value=0>
|
||||
|
||||
<button id="coconut-button"> Get Coconut Credential </button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
Create here the demo
|
||||
<span id="output"></span>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ module.exports = {
|
||||
patterns: [
|
||||
'index.html',
|
||||
{
|
||||
from: '../../../dist/wasm/credentials/*.(js|wasm)',
|
||||
from: '../pkg/*.(js|wasm)',
|
||||
to: '[name][ext]',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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==
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<WasmCoconutCredential, WasmCredentialClientError> {
|
||||
// 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<CoconutCredential> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<dyn std::error::Error> },
|
||||
|
||||
// #[error("Coin parse error")]
|
||||
// CoinParseError,
|
||||
// #[error("State error")]
|
||||
// StateError,
|
||||
#[error("The provided mnemonic was malformed: {source}")]
|
||||
MalformedMnemonic {
|
||||
#[from]
|
||||
source: bip39::Error,
|
||||
},
|
||||
}
|
||||
|
||||
wasm_error!(WasmCredentialClientError);
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<bip39::Mnemonic, WasmCredentialClientError> {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
+18
-110
@@ -1,114 +1,22 @@
|
||||
use std::str::FromStr;
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<State>,
|
||||
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<WasmCredentialClient, WasmCredentialClientError> {
|
||||
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<Signature, WasmCredentialClientError> {
|
||||
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<DirectSigningHttpRpcNyxdClient, NyxdError> {
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<NymNetworkDetails>,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub use_sandbox: Option<bool>,
|
||||
}
|
||||
Reference in New Issue
Block a user