Compare commits

...

18 Commits

Author SHA1 Message Date
Jędrzej Stuczyński d5c598729b updated credential client 2024-04-09 17:03:59 +01:00
Mark Sinclair 7eb764cd5a Fix typing 2024-04-09 16:47:53 +01:00
Mark Sinclair 854128ee21 Fix up args to pass isSandbox 2024-04-09 16:47:53 +01:00
Mark Sinclair 6cd59124a2 Get ready for publishing Typescript SDK with credentials client 2024-04-09 16:47:53 +01:00
Gala d5cc31b7f0 remove commented code 2024-04-09 16:47:53 +01:00
Gala ea6f009c01 wip credential generation example 2024-04-09 16:47:52 +01:00
Gala 5881f6b6aa wip credentials sdk example 2024-04-09 16:47:52 +01:00
Gala aafddc78d1 sdk cononut compilation an wip coco example 2024-04-09 16:47:49 +01:00
Gala 29a0e8620c fixing typo 2024-04-09 16:47:41 +01:00
Gala 800016f682 Nym credentials sdk
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2024-04-09 16:47:38 +01:00
Jędrzej Stuczyński 6151f15e7e missing panic hook feature 2024-04-09 16:46:53 +01:00
Jędrzej Stuczyński 5e6a945eef apply changes from 34c00a150140a55a1fc1c9b9f6118584f8bbfc0f 2024-04-09 16:46:53 +01:00
Jędrzej Stuczyński aec23a4f83 fixing panic hook weirdness 2024-04-09 16:46:52 +01:00
Jędrzej Stuczyński 3d563ece79 fixed browser storage extension build 2024-04-09 16:46:52 +01:00
Jędrzej Stuczyński f24a0dc8ae removed dead code 2024-04-09 16:46:52 +01:00
Jędrzej Stuczyński 50fbdb4f0b implemented acquireCredential method in the wasm credential client 2024-04-09 16:46:49 +01:00
Gala d445a26999 wip internal-dev 2024-04-09 16:41:59 +01:00
Mark Sinclair 30294062af WASM client for credentials - wip 2024-04-09 16:41:55 +01:00
56 changed files with 11252 additions and 3752 deletions
Generated
+27 -1
View File
@@ -5520,6 +5520,30 @@ dependencies = [
"tracing",
]
[[package]]
name = "nym-credential-client-wasm"
version = "1.3.0-rc.0"
dependencies = [
"bip39",
"js-sys",
"nym-bandwidth-controller",
"nym-bin-common",
"nym-credential-storage",
"nym-credentials",
"nym-credentials-interface",
"nym-network-defaults",
"nym-validator-client",
"serde",
"serde-wasm-bindgen",
"thiserror",
"tsify",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"wasm-utils",
"zeroize",
]
[[package]]
name = "nym-credential-storage"
version = "0.1.0"
@@ -6090,7 +6114,9 @@ dependencies = [
"schemars",
"serde",
"thiserror",
"tsify",
"url",
"wasm-bindgen",
]
[[package]]
@@ -11039,7 +11065,6 @@ name = "wasm-client-core"
version = "0.1.0"
dependencies = [
"async-trait",
"console_error_panic_hook",
"js-sys",
"nym-bandwidth-controller",
"nym-client-core",
@@ -11114,6 +11139,7 @@ dependencies = [
name = "wasm-utils"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"futures",
"getrandom 0.2.10",
"gloo-net",
+7
View File
@@ -122,6 +122,7 @@ members = [
# "wasm/full-nym-wasm",
"wasm/mix-fetch",
"wasm/node-tester",
"wasm/credentials"
]
default-members = [
@@ -235,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"
+1
View File
@@ -103,6 +103,7 @@ sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint
sdk-wasm-build:
$(MAKE) -C nym-browser-extension/storage wasm-pack
$(MAKE) -C wasm/client
$(MAKE) -C wasm/credentials
$(MAKE) -C wasm/node-tester
$(MAKE) -C wasm/mix-fetch
#$(MAKE) -C wasm/full-nym-wasm
@@ -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())
}
}
+1 -2
View File
@@ -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"] }
@@ -33,6 +33,12 @@ impl CoconutCredentialManager {
}
}
pub async fn take_credentials(self) -> Vec<StoredIssuedCredential> {
let mut inner = self.inner.write().await;
inner.credential_usage = Vec::new();
std::mem::take(&mut inner.credentials)
}
pub async fn insert_issued_credential(
&self,
credential_type: String,
@@ -15,6 +15,12 @@ pub struct EphemeralStorage {
coconut_credential_manager: CoconutCredentialManager,
}
impl EphemeralStorage {
pub async fn take_credentials(self) -> Vec<StoredIssuedCredential> {
self.coconut_credential_manager.take_credentials().await
}
}
impl Default for EphemeralStorage {
fn default() -> Self {
EphemeralStorage {
-13
View File
@@ -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 {
+8
View File
@@ -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"]
+91 -7
View File
@@ -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> {
+1 -7
View File
@@ -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"]
+7
View File
@@ -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
@@ -29,6 +35,7 @@ optional = true
[features]
default = ["sleep"]
panic-hook = ["console_error_panic_hook"]
sleep = ["web-sys", "web-sys/Window"]
websocket = [
"getrandom",
+12
View File
@@ -70,3 +70,15 @@ pub async fn sleep(ms: i32) -> Result<(), wasm_bindgen::JsValue> {
js_fut.await?;
Ok(())
}
#[wasm_bindgen]
#[cfg(feature = "panic-hook")]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
console_error_panic_hook::set_once();
}
+1 -1
View File
@@ -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" }
-16
View File
@@ -12,19 +12,3 @@ pub use error::ExtensionStorageError;
#[cfg(target_arch = "wasm32")]
pub use storage::ExtensionStorage;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
+11
View File
@@ -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",
+7 -1
View File
@@ -15,7 +15,13 @@
"nym-connect/**",
"explorer",
"types",
"clients/validator"
"clients/validator",
"sdk/typescript/packages/**",
"sdk/typescript/examples/**",
"sdk/typescript/codegen/**",
"sdk/typescript/packages/**",
"sdk/typescript/examples/**",
"sdk/typescript/codegen/**"
],
"scripts": {
"nuke": "npx rimraf **/node_modules node_modules",
@@ -0,0 +1,28 @@
# Nym credential generation Usage Example
This is a simple project to show you how to use nym credential generation.
```ts
import { mixFetch } from '@nymproject/mix-fetch';
// HTTP GET
const response = await mixFetch('https://nymtech.net');
const html = await response.text();
// HTTP POST
const apiResponse = await mixFetch('https://api.example.com', {
method: 'POST',
body: JSON.stringify({ foo: 'bar' }),
headers: { [`Content-Type`]: 'application/json', Authorization: `Bearer ${AUTH_TOKEN}` }
});
```
## Running the example
```
npm install
npm run start
```
Open a browser at http://localhost:1234 and as the example loads, a connection will be made to the Nym Mixnet
and a text file and image will be downloaded and displayed in the browser.
@@ -0,0 +1,16 @@
{
"name": "@nymproject/nym-credentials-example-parcel",
"version": "1.0.4-rc.2",
"license": "Apache-2.0",
"scripts": {
"build": "parcel build --no-cache --no-content-hash",
"serve": "serve dist",
"start": "parcel --no-cache"
},
"dependencies": {
"@nymproject/sdk": "1.2.4-rc.2",
"parcel": "^2.9.3"
},
"private": false,
"source": "src/index.html"
}
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Internal Credential Tester</title>
<script type="module" src="./index.ts"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
padding: 2rem;
}
</style>
</head>
<body>
<div>
<h1>Credential</h1>
<input id="mnemonic" placeholder="mnemonic"></input>
<input id="coin" placeholder="amount" value="1unym"></input>
<button id="button">Get Credential</button>
<div id="credential"></div>
</div>
</body>
</html>
@@ -0,0 +1,38 @@
import { createNymCredentialsClient } from '@nymproject/sdk';
import { appendOutput } from './utils';
async function main() {
const mnemonic = document.getElementById('mnemonic') as HTMLInputElement;
if (process.env.MNEMONIC) {
mnemonic.defaultValue = process.env.MNEMONIC;
}
const coin = document.getElementById('coin') as HTMLInputElement;
const button = document.getElementById('button') as HTMLButtonElement;
const client = await createNymCredentialsClient();
const generateCredential = async () => {
const amount = coin.value;
const mnemonicString = mnemonic.value;
console.log({ amount, mnemonicString });
try {
appendOutput('About to get a credential... 🥁');
const credential = await client.comlink.acquireCredential(amount, mnemonicString, { useSandbox: true }); // options: {useSandbox?: boolean; networkDetails?: {}}
appendOutput('Success! 🎉');
appendOutput(JSON.stringify(credential, null, 2));
} catch (e) {
console.error('Failed to get credential', e);
appendOutput((e as any).message);
}
};
if (button) {
button.addEventListener('click', () => generateCredential());
}
}
// wait for the html to load
window.addEventListener('DOMContentLoaded', () => {
// let's do this!
main();
});
@@ -0,0 +1,6 @@
export function appendOutput(value: string) {
const el = document.getElementById('credential') as HTMLPreElement;
const text = document.createTextNode(`${value}\n`);
el.appendChild(text);
}
+1
View File
@@ -1,2 +1,3 @@
src/mixnet/wasm/worker.js
src/coconut/worker.js
docs/
+3 -1
View File
@@ -13,7 +13,8 @@
"scripts": {
"build": "scripts/build-prod.sh",
"build:dev": "scripts/build.sh",
"build:dev:esm": "scripts/build-dev-esm.sh",
"build:dev:esm": "SDK_DEV_MODE=true scripts/build-dev-esm.sh",
"build:dev:esm:no-inline": "scripts/build-dev-esm.sh",
"build:worker": "rollup -c rollup-worker.config.mjs",
"clean": "rimraf dist",
"docs:dev": "run-p docs:watch docs:serve ",
@@ -32,6 +33,7 @@
},
"dependencies": {
"@nymproject/nym-client-wasm": ">=1.2.4-rc.2 || ^1",
"@nymproject/nym-credential-client-wasm": ">=1.2.0-rc.9 || ^1",
"comlink": "^4.3.1"
},
"devDependencies": {
@@ -0,0 +1,7 @@
import { getConfig } from './rollup/worker.mjs';
export default {
...getConfig('src/coconut/worker.ts', 'nym_credential_client_wasm_bg.wasm'),
inlineWasm: true,
format: 'cjs',
};
@@ -0,0 +1,7 @@
import { getConfig } from './rollup/worker.mjs';
export default {
...getConfig('src/coconut/worker.ts', 'nym_credential_client_wasm_bg.wasm', {
inlineWasm: process.env.SDK_DEV_MODE === 'true',
}),
};
@@ -2,6 +2,6 @@ import { getConfig } from './rollup/esm.mjs';
export default {
...getConfig({
inline: false,
inline: process.env.SDK_DEV_MODE === 'true',
}),
};
@@ -0,0 +1,8 @@
import { getConfig } from './rollup/worker.mjs';
export default {
...getConfig('src/mixnet/wasm/worker.ts', 'nym_client_wasm_bg.wasm', {
inlineWasm: true,
format: 'cjs',
}),
};
@@ -1,26 +1,7 @@
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import { wasm } from '@rollup/plugin-wasm';
import replace from '@rollup/plugin-replace';
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
import { getConfig } from './rollup/worker.mjs';
export default {
input: 'src/mixnet/wasm/worker.ts',
output: {
dir: 'dist',
format: 'cjs',
},
plugins: [
resolve({ extensions }),
// this is some nasty monkey patching that removes the WASM URL (because it is handled by the `wasm` plugin)
replace({
values: { "input = new URL('nym_client_wasm_bg.wasm', import.meta.url);": 'input = undefined;' },
delimiters: ['', ''],
preventAssignment: true,
}),
// force the wasm plugin to embed the wasm bundle - this means no downstream bundlers have to worry about handling it
wasm({ maxFileSize: 10000000, targetEnv: 'browser' }),
typescript({ compilerOptions: { declaration: false, target: 'es5' } }),
],
...getConfig('src/mixnet/wasm/worker.ts', 'nym_client_wasm_bg.wasm', {
inlineWasm: process.env.SDK_DEV_MODE === 'true',
}),
};
+1 -1
View File
@@ -21,7 +21,7 @@ export const getConfig = (opts) => ({
webWorkerLoader({ targetPlatform: 'browser', inline: opts.inline }), // the inline param is used here
resolve({ extensions }),
typescript({
exclude: ['mixnet/wasm/worker.ts'],
exclude: ['mixnet/wasm/worker.ts', 'coconut/worker.ts'],
compilerOptions: { outDir: opts.outputDir || 'dist/esm' },
}),
],
@@ -0,0 +1,43 @@
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import { wasm } from '@rollup/plugin-wasm';
import replace from '@rollup/plugin-replace';
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
/**
* Configure worker output
*
* @param opts
* `format`: `es` or `cjs`,
* `inlineWasm`: true or false,
* `tsTarget`: `es5` or `es6`
*/
export const getConfig = (input, wasmFilename, opts) => ({
input,
output: {
dir: 'dist',
format: opts?.format || 'es',
},
plugins: [
resolve({ extensions }),
// this is some nasty monkey patching that removes the WASM URL (because it is handled by the `wasm` plugin)
replace({
values: { [`input = new URL('${wasmFilename}', import.meta.url);`]: 'input = undefined;' },
delimiters: ['', ''],
preventAssignment: true,
}),
opts?.inlineWasm === true
? wasm({ maxFileSize: 10_000_000, targetEnv: 'browser' }) // force the wasm plugin to embed the wasm bundle - this means no downstream bundlers have to worry about handling it
: wasm({
targetEnv: 'browser',
fileName: '[name].wasm',
}),
typescript({
compilerOptions: {
declaration: false,
target: opts?.tsTarget || 'es6',
},
}),
],
});
@@ -14,7 +14,21 @@ set -o pipefail
rollup -c rollup-worker.config.mjs
# move it next to the Typescript `src/index.ts` so it can be inlined by rollup
cp dist/worker.js src/worker/worker.js || true
cp dist/worker.js src/mixnet/wasm/worker.js || true
rm dist/worker.js || true
#-------------------------------------------------------
# WEB WORKER (COCONUT WASM)
#-------------------------------------------------------
# The web worker needs to be bundled because the WASM bundle needs to be loaded synchronously and all dependencies
# must be included in the worker script (because it is not loaded as an ES Module)
# build the worker
rollup -c rollup-coconut-worker.config.mjs
# move it next to the Typescript `src/index.ts` so it can be inlined by rollup
mkdir dist/esm || true
cp dist/worker.js src/coconut/worker.js || true
rm dist/worker.js || true
#-------------------------------------------------------
@@ -19,6 +19,19 @@ rollup -c rollup-worker.config.mjs
rm -f src/mixnet/wasm/worker.js
mv dist/worker.js src/mixnet/wasm/worker.js
#-------------------------------------------------------
# WEB WORKER (COCONUT WASM)
#-------------------------------------------------------
# The web worker needs to be bundled because the WASM bundle needs to be loaded synchronously and all dependencies
# must be included in the worker script (because it is not loaded as an ES Module)
# build the worker
rollup -c rollup-coconut-worker.config.mjs
# move it next to the Typescript `src/index.ts` so it can be inlined by rollup
cp dist/worker.js src/coconut/worker.js || true
rm dist/worker.js || true
#-------------------------------------------------------
# ESM
#-------------------------------------------------------
@@ -1,7 +1,41 @@
// eslint-disable-next-line no-console
import * as Comlink from 'comlink';
import InlineWasmWebWorker from 'web-worker:./worker';
import { EventKinds, INymCredentialClientWebWorker, NymCredentialsClient } from './types';
export const createNymCredentialsClient = async (): Promise<NymCredentialsClient> => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const worker = await createWorker();
// let comlink handle interop with the web worker
const comlink = Comlink.wrap<INymCredentialClientWebWorker>(worker);
return { comlink };
};
/**
* @ignore
* @internal
* Async method to create a web worker that runs the Nym credentials client on another thread. It will only return once the worker
* has passed back a `Loaded` event to the calling thread.
*
* @return The instance of the web worker.
*/
export const notImplementedYet = () => console.log('Not implement, coming soon...');
const createWorker = async () =>
new Promise<Worker>((resolve, reject) => {
// rollup will inline the built worker script, so that when the SDK is used in
// other projects, they will not need to mess around trying to bundle it
// however, it will make this SDK bundle bigger because of Base64 inline data
const worker = new InlineWasmWebWorker();
worker.addEventListener('error', reject);
worker.addEventListener(
'message',
(msg) => {
worker.removeEventListener('error', reject);
if (msg.data?.kind === EventKinds.Loaded) {
resolve(worker);
} else {
reject(msg);
}
},
{ once: true },
);
});
@@ -0,0 +1,34 @@
/**
* Enum representing various event kinds.
* @enum
*/
export enum EventKinds {
Loaded = 'Loaded',
}
export interface LoadedEvent {
kind: EventKinds.Loaded;
args: {
loaded: true;
};
}
export type Credential = any; // TODO
export interface CredentialClientOpts {
useSandbox?: boolean;
networkDetails?: {};
}
export interface INymCredentialClientWebWorker {
acquireCredential: (coin: string, mnemonic: string, opts: CredentialClientOpts) => Promise<Credential>;
}
// export interface NymCredentialsClient {
// init: (mnemonic: string) => void;
// acquireCredential: (coin: Coin, mnemonic: string, options?: CredentialsClientOpts) => Promise<Credentials>;
// }
export interface NymCredentialsClient {
comlink: INymCredentialClientWebWorker;
}
@@ -0,0 +1,50 @@
/* eslint-disable no-restricted-globals */
import * as Comlink from 'comlink';
//
// Rollup will replace wasmBytes with a function that loads the WASM bundle from a base64 string embedded in the output.
//
// Doing it this way, saves having to support a large variety of bundlers and their quirks.
//
// @ts-ignore
// eslint-disable-next-line import/no-extraneous-dependencies
import wasmBytes from '@nymproject/nym-credential-client-wasm/nym_credential_client_wasm_bg.wasm';
import init, { acquireCredential } from '@nymproject/nym-credential-client-wasm/nym_credential_client_wasm';
import type { INymCredentialClientWebWorker, CredentialClientOpts, LoadedEvent } from './types';
import { EventKinds } from './types';
/**
* Helper method to send typed messages.
* @param event The strongly typed message to send back to the calling thread.
*/
// eslint-disable-next-line no-restricted-globals
const postMessageWithType = <E>(event: E) => self.postMessage(event);
console.log('[Nym WASM client for Credentials] Starting Nym WASM web worker...');
// load WASM binary
async function main() {
// rollup with provide a function to get the mixFetch WASM bytes
const bytes = await wasmBytes();
// load rust WASM package
const wasmPackage = await init(bytes);
console.log('Loaded RUST WASM');
wasmPackage.set_panic_hook();
const webWorker: INymCredentialClientWebWorker = {
async acquireCredential(coin: string, mnemonic: string, opts: CredentialClientOpts) {
console.log('[Worker] --- acquireCredential ---', { coin, mnemonic, opts });
return acquireCredential(mnemonic, coin, opts);
},
};
// start comlink listening for messages and handle them above
Comlink.expose(webWorker);
// notify any listeners that the web worker has loaded and is ready for testing
postMessageWithType<LoadedEvent>({ kind: EventKinds.Loaded, args: { loaded: true } });
}
main().catch((e: any) => console.error('Unhandled exception in credential worker', e));
+119
View File
@@ -0,0 +1,119 @@
Arguments:
/Users/galacalero/.nvm/versions/node/v16.19.0/bin/node /Users/galacalero/.yarn/bin/yarn.js install
PATH:
/Users/galacalero/.rvm/gems/ruby-2.7.5/bin:/Users/galacalero/.rvm/gems/ruby-2.7.5@global/bin:/Users/galacalero/.rvm/rubies/ruby-2.7.5/bin:/Users/galacalero/.rvm/bin:/Users/galacalero/.pyenv/shims:/Users/galacalero/.nvm/versions/node/v16.19.0/bin:/Users/galacalero/.yarn/bin:/Users/galacalero/.config/yarn/global/node_modules/.bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Applications/Keybase.app/Contents/SharedSupport/bin:/Library/Apple/usr/bin:/Applications/Postgres.app/Contents/Versions/latest/bin:/Users/galacalero/.cargo/bin
Yarn version:
1.22.18
Node version:
16.19.0
Platform:
darwin arm64
Trace:
Error: https://registry.yarnpkg.com/@nymproject%2feslint-config-react-typescript: Not found
at Request.params.callback [as _callback] (/Users/galacalero/.yarn/lib/cli.js:66138:18)
at Request.self.callback (/Users/galacalero/.yarn/lib/cli.js:140883:22)
at Request.emit (node:events:513:28)
at Request.<anonymous> (/Users/galacalero/.yarn/lib/cli.js:141855:10)
at Request.emit (node:events:513:28)
at IncomingMessage.<anonymous> (/Users/galacalero/.yarn/lib/cli.js:141777:12)
at Object.onceWrapper (node:events:627:28)
at IncomingMessage.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1358:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
npm manifest:
{
"name": "@nymproject/sdk",
"version": "1.2.4-rc.2",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
"files": [
"dist/esm/worker.js",
"dist/cjs/worker.js",
"dist/**/*"
],
"main": "dist/cjs/index.js",
"browser": "dist/esm/index.js",
"scripts": {
"build": "scripts/build-prod.sh",
"build:dev": "scripts/build.sh",
"build:dev:esm": "scripts/build-dev-esm.sh",
"build:worker": "rollup -c rollup-worker.config.mjs",
"clean": "rimraf dist",
"docs:dev": "run-p docs:watch docs:serve ",
"docs:generate": "typedoc",
"docs:generate:prod": "typedoc --basePath ./docs/tsdoc/nymproject/sdk/",
"docs:prod:build": "scripts/build-prod-docs-collect.sh",
"docs:serve": "reload -b -d ./docs -p 3000",
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"yarn docs:generate\"",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"prebuild": "node scripts/showDependencyLocation.cjs",
"start": "tsc -w",
"start:dev": "nodemon --watch src -e ts,json --exec 'yarn build:dev:esm'",
"test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js -c=jest.config.mjs --no-cache",
"tsc": "tsc --noEmit true"
},
"dependencies": {
"@nymproject/nym-client-wasm": ">=1.2.4-rc.2 || ^1",
"comlink": "^4.3.1"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-inject": "^5.0.3",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.2.1",
"@rollup/plugin-typescript": "^10.0.1",
"@rollup/plugin-url": "^8.0.1",
"@rollup/plugin-wasm": "^6.1.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"handlebars": "^4.7.8",
"jest": "^29.5.0",
"nodemon": "3.0.1",
"reload": "^3.2.1",
"rimraf": "^3.0.2",
"rollup": "^3.9.1",
"rollup-plugin-base64": "^1.0.1",
"rollup-plugin-web-worker-loader": "^1.6.1",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.2",
"typedoc": "^0.24.8",
"typescript": "^4.8.4"
},
"private": false,
"type": "module",
"types": "./dist/esm/index.d.ts"
}
yarn manifest:
No manifest
Lockfile:
No lockfile
+43
View File
@@ -0,0 +1,43 @@
[package]
name = "nym-credential-client-wasm"
authors = []
version = "1.3.0-rc.0"
edition = "2021"
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."
rust-version = "1.56"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
bip39 = { workspace = true }
#futures = { workspace = true }
js-sys = { workspace = true }
#rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] }
#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-utils = { path = "../../common/wasm/utils", features = ["panic-hook"] }
nym-bin-common = { path = "../../common/bin-common" }
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", default-features = false }
nym-credentials-interface = { path = "../../common/credentials-interface" }
nym-network-defaults = { path = "../../common/network-defaults" }
[dev-dependencies]
wasm-bindgen-test = "0.3.36"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
+7
View File
@@ -0,0 +1,7 @@
build:
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/credentials
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
+2
View File
@@ -0,0 +1,2 @@
node_modules
dist
+5
View File
@@ -0,0 +1,5 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import('./index.js')
.catch(e => console.error('Error importing `index.js`:', e));
+34
View File
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym Credentials WebAssembly Demo</title>
<script src="bootstrap.js"></script>
</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>
<span id="output"></span>
</p>
</body>
</html>
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
class WebWorkerClient {
worker = null;
constructor() {
this.worker = new Worker('./worker.js');
this.worker.onmessage = (ev) => {
if (ev.data && ev.data.kind) {
switch (ev.data.kind) {
case 'ReceivedCredential':
const { credential } = ev.data.args;
displayCredential(credential)
break;
}
}
};
}
getCredential = (amount, mnemonic) => {
if (!this.worker) {
console.error('Could not get credential because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'GetCredential',
args: {
amount,
mnemonic
},
});
};
}
let client = null;
async function main() {
client = new WebWorkerClient();
const coconutButton = document.querySelector('#coconut-button');
coconutButton.onclick = function () {
getCredential();
};
}
async function getCredential() {
const amount = document.getElementById('credential-amount').value;
const mnemonic = document.getElementById('mnemonic').value;
await client.getCredential(amount, mnemonic);
}
function displayCredential(credential) {
console.log("got credential", credential)
let timestamp = new Date().toISOString().substr(11, 12);
let credentialDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
let paragraphContent = document.createTextNode(timestamp + ' 🥥🥥🥥 >>> ' + JSON.stringify(credential));
paragraph.appendChild(paragraphContent);
credentialDiv.appendChild(paragraph);
document.getElementById('output').appendChild(credentialDiv);
}
main();
@@ -0,0 +1,40 @@
{
"name": "create-wasm-app",
"version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages",
"main": "index.js",
"bin": {
"create-wasm-app": ".bin/create-wasm-app.js"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"build:wasm": "cd ../ && make wasm-build",
"start": "webpack-dev-server --port 8001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
},
"keywords": [
"webassembly",
"wasm",
"rust",
"webpack"
],
"author": "Gala Calero <https://github.com/gala1234>",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/nymtech/nym/issues"
},
"homepage": "https://nymtech.net/docs",
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"hello-wasm-pack": "^0.1.0",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-credential-client-wasm": "file:../pkg"
}
}
@@ -0,0 +1,46 @@
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");
module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
entry: {
bootstrap: "./bootstrap.js",
worker: "./worker.js",
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
},
mode: "development",
// mode: 'production',
plugins: [
new CopyWebpackPlugin({
patterns: [
"index.html",
{
from: "../pkg/*.(js|wasm)",
to: "[name][ext]",
},
],
}),
],
devServer: {
proxy: {
'/api': {
target: 'https://sandbox-nym-api1.nymtech.net/',
secure: false,
},
},
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
},
experiments: { syncWebAssembly: true },
};
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const RUST_WASM_URL = "nym_credential_client_wasm_bg.wasm"
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 {
acquireCredential,
} = wasm_bindgen;
async function testGetCredential() {
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'GetCredential': {
const { amount, mnemonic } = event.data.args;
// 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
}
})
}
}
}
};
}
async function main() {
console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN START");
// load rust WASM package
await wasm_bindgen(RUST_WASM_URL);
console.log('Loaded RUST WASM');
// run test on simplified and dedicated tester:
await testGetCredential();
//
console.log(">>>>>>>>>>>>>>>>>>>>> JS WORKER MAIN END")
}
// Let's get started!
main();
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+123
View File
@@ -0,0 +1,123 @@
// 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::StoredIssuedCredential;
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;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[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<WasmIssuedCredential, 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?;
let blinded_serial = deposit_state.voucher.blinded_serial_number_bs58();
console_log!(
"obtained bandwidth voucher with the following blinded serial number: {blinded_serial}"
);
// 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_bandwidth_voucher(
&deposit_state,
&client,
&ephemeral_storage,
)
.await?;
// and immediately get it out!
let mut credentials = ephemeral_storage.take_credentials().await;
let cred = credentials.pop().expect("we just got a credential issued");
Ok(cred.into())
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct WasmIssuedCredential {
pub serialization_revision: u8,
pub credential_data: Vec<u8>,
pub credential_type: String,
pub epoch_id: u32,
}
impl From<StoredIssuedCredential> for WasmIssuedCredential {
fn from(value: StoredIssuedCredential) -> Self {
WasmIssuedCredential {
serialization_revision: value.serialization_revision,
credential_data: value.credential_data.clone(),
credential_type: value.credential_type.clone(),
epoch_id: value.epoch_id,
}
}
}
+61
View File
@@ -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);
+39
View File
@@ -0,0 +1,39 @@
// 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,
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,
}
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_arch = "wasm32")]
mod credential;
#[cfg(target_arch = "wasm32")]
mod error;
#[cfg(target_arch = "wasm32")]
mod helpers;
#[cfg(target_arch = "wasm32")]
mod opts;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
#[cfg(target_arch = "wasm32")]
pub fn main() {
wasm_utils::console_log!("[rust main]: rust module loaded");
wasm_utils::console_log!(
"credential client version used: {:#?}",
nym_bin_common::bin_info!()
);
wasm_utils::console_log!("[rust main]: setting panic hook");
wasm_utils::set_panic_hook();
}
+17
View File
@@ -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>,
}
+5555 -3663
View File
File diff suppressed because it is too large Load Diff