Compare commits

...

39 Commits

Author SHA1 Message Date
serinko 7686c60333 DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:46:43 +00:00
serinko 98c03ffa56 DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:41:44 +00:00
serinko 0a3a31dedc DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:40:27 +00:00
serinko 79ae9b69ef DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:39:17 +00:00
serinko 549b58311d Merge pull request #3906 from nymtech/patch/documentation/syntax-fix
Fixing bugs in mdbook build errors (links, admonish, path) -> CI/CD runs without a problem.
2023-09-19 15:52:57 +00:00
serinko 279b494a60 corrected surbs line 2023-09-19 12:54:36 +02:00
serinko 3be0a6cf65 fixed broken code path 2023-09-19 10:51:03 +02:00
serinko 9b8add1daa fixed broken links 2023-09-19 10:36:25 +02:00
Mark Sinclair 2193378d42 Merge pull request #3905 from nymtech/bugfix/ci-cd-docs
Docs: make shell scripts exit on errors so that CI jobs fail on build errors
2023-09-19 09:28:33 +01:00
Mark Sinclair c57263e91b Docs: make shell scripts exit on errors so that CI jobs fail on build errors 2023-09-19 09:22:33 +01:00
Mark Sinclair 685f26792f Typescript SDK Nextra Docs (#3880)
* Remove pnpm-lock.yaml

* Add initial documentation

* updating packages and disconnecting on mixFetch when unmount

* handle the mixFetch error

* remove the mixfetch disconexion

* using now rc5 version

* Update overview

* Update installation page

* wip startong

* Copy edits and improving some of the formatting and styling

* Improve naming

* Add CosmosKit example

* Linting

* Update next.js

* Remove lock file

* More CosmosKit docs

* wip

* cleaninig a bit

* quick fix for wallet error

* wip wallet ui

* wip wallet ui

* more wallet ui

* fixing key error

* wip

* Example code

* Add custom style for code blocks to limit their height and scroll

* Change bg on darkmode

* Add styling to darkmode

* Reorg CSS - tbc

* Move example code

* Ledger support in Cosmos Kit - wip

* Change default app to Typescript

* Remove static export

* Tidy up wallet UI

* Set theme colour by hue

* Force dark mode theme

* some wallet ui

* Style buttons sidebar

* Sidebar colors

* Links styling

* Style callouts

* Add styling to button, chips, progress motion component

* Style agenda

* adding loaders

* wallet loaders

* traffic styles

* Fix colours

* Add links to methods

* Add execute code block

* Add traffic codeblock

* Add mixfetch codeblock

* Add Cosmokit codeblock

* Update info on getting started

* fixing build

* Fix build error

* Fix theme

* Fix filenames on examples

* Add copy to CosmosKit example

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Gala <calero.vg@gmail.com>
Co-authored-by: Lorexia <alexia.lorenza.martinel@protonmail.com>
2023-09-18 19:46:32 +01:00
Jon Häggblad 109659152d Remove leftover .faq files 2023-09-18 17:11:18 +02:00
benedetta davico 73a75f7aef Master (#3888)
* removed old wallet address flag again

* Add updates to community list projects

* Update cd-docs.yml

* Update cd-docs.yml

* [hotfix]: don't assign invalid fields when crossing the JS boundary (#3805)

* [hotfix]: don't assign invalid fields when crossing the JS boundary

* eslint

* changelog update and version bump

* changed last vers. checkout to master

* corrected path of config

* make binaries executable

* docs: typescript.md - changing variables

* docs: rust.md - changing variables

* docs: vesting-contract.md - changing variables

* docs: mixnet-contract.md - changing variables

* docs: all variables changed

* operators: all variables finished

* dev-portal: mixnet-integration.md - variable changed

* dev-portal: faq.md - variable changed

* dev-portal: moredo.md up to date w NC default

* dev-portal: telegram.md - added banner & minor fix

* dev-portal: matrix.md - added banner

* PR finished - ready for review and merge

* removed all instances of platform_release_version var

* removed all instances of platform_release_version var

* changed version bumper script: removed platform_release_version references

* changed comment

* updating changelog and bumping versions

* Docs: new post-processing for books so that assets stay relative

This commit has the same content as https://github.com/nymtech/nym/pull/3842

* Docs: add prod deploy settings

* fixed ChangeMixCostParams event deserialization (#3873)

* Merge pull request #3892 from nymtech/feature/operators/smoosh-faq

Create smoosh FAQ section & re-organize operators/faq accordingly

* corrected faq dir path

* added integrations-faq page

---------

Co-authored-by: mfahampshire <maxhampshire@pm.me>
Co-authored-by: Lorexia <alexia.lorenza.martinel@protonmail.com>
Co-authored-by: Tommy Verrall <tommy@nymtech.net>
Co-authored-by: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com>
Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: serinko <97586125+serinko@users.noreply.github.com>
Co-authored-by: mx <33262279+mfahampshire@users.noreply.github.com>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Tommy Verrall <tommyvez@protonmail.com>
2023-09-18 17:06:10 +02:00
benedettadavico 06956efa3f Deleting faq.md on develop 2023-09-18 17:04:58 +02:00
serinko f130ff73ad Merge pull request #3900 from nymtech/patch/dev-portal/integrations-faq-fix
integrations added to developers/faq section
2023-09-18 13:40:45 +00:00
serinko ddaabdc856 Merge pull request #3892 from nymtech/feature/operators/smoosh-faq
Create smoosh FAQ section & re-organize operators/faq accordingly
2023-09-18 13:38:54 +00:00
serinko 65de9d146d removed redundant .faq dir 2023-09-18 12:20:19 +02:00
serinko ec3aa7d8eb time frame in intro & ETA delete 2023-09-18 11:58:00 +02:00
serinko 805e1b8759 add NymAPI codebase link 2023-09-18 11:52:53 +02:00
serinko 53db649e84 steps timing explained & italic removed 2023-09-18 11:44:50 +02:00
serinko f76092e8e7 clarified new smooshed gateway 2023-09-18 11:40:58 +02:00
serinko c75fda0eb1 fix SUMMARY.md & links 2023-09-18 10:48:27 +02:00
serinko 9dfbc8c9c8 integrations added to developers/faq section 2023-09-18 10:20:48 +02:00
serinko 0171791188 syntax fix 2023-09-18 07:59:19 +00:00
serinko f6a9d0b843 formatting quotes 2023-09-18 07:45:14 +00:00
serinko 3c750f61e5 syntax fix 2023-09-15 19:14:31 +02:00
serinko a8e7c3ca49 syntax fix 2023-09-15 19:13:11 +02:00
serinko 4f39630861 spell check 2023-09-15 13:39:34 +02:00
serinko 63714092df gathered community questions & nym team answers 2023-09-15 13:33:26 +02:00
serinko cd89f4866a initialize faq/smoosh-faq.md page 2023-09-15 11:58:32 +02:00
serinko a94c4c0895 initialize FAQ section & move faq -> mixnodes-faq 2023-09-15 11:51:24 +02:00
Jędrzej Stuczyński 63b0658c65 [feat] Socks5 and Native client: run with hardcoded topology (#3866)
* allow running clients using hardcoded topology

* fixed sdk/lib/socks5-listener build

* fixed nym-connect build

* allow for both snake_case and camelCase deserialization
2023-09-14 14:26:11 +02:00
Tommy Verrall 6b161700f6 Merge pull request #3882 from nymtech/remove_unecessary_workflows
Github actions: remove nightly builds workflows on latest releases
2023-09-14 13:52:59 +02:00
Tommy Verrall dcfe5f7c5b Merge pull request #3883 from nymtech/ci/fix-clippy-error
fix ci failing builds on clippy errors
2023-09-14 11:31:02 +02:00
Tommy Verrall cd89e26b74 fix ci failing builds on clippy errors 2023-09-14 10:13:32 +02:00
Jon Häggblad 4f9df2a8b1 Update Cargo.lock that was missed during release 2023-09-13 22:28:26 +02:00
Jędrzej Stuczyński f6a4fc3b6f updated mixnet contract schema files 2023-09-13 15:16:08 +01:00
Raphaël Walther 899db660ce Github actions: remove nightly builds workflows on latest releases 2023-09-13 10:22:41 +02:00
Jon Häggblad ec0ac56b8a Improve error handling in wireguard listener (#3881) 2023-09-13 08:19:52 +02:00
113 changed files with 3691 additions and 10176 deletions
-191
View File
@@ -1,191 +0,0 @@
name: Nightly builds on latest release
on:
schedule:
- cron: '14 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-20.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-20.04
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+-' | sort -V | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
if: matrix.os == 'ubuntu-20.04'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Build all examples
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --examples
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace -- --ignored
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: [build,get_release]
runs-on: custom-runner-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: install npm
uses: actions/setup-node@v3
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 18
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -1,191 +0,0 @@
name: Nightly builds on second latest release
on:
schedule:
- cron: '24 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-20.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-20.04
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
if: matrix.os == 'ubuntu-20.04'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Build all examples
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --examples
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace -- --ignored
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: [build,get_release]
runs-on: custom-runner-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: install npm
uses: actions/setup-node@v3
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 18
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
Generated
+8
View File
@@ -7102,6 +7102,7 @@ dependencies = [
"nym-sphinx",
"nym-topology",
"pretty_env_logger",
"rand 0.7.3",
"serde",
"serde_json",
"tap",
@@ -7392,6 +7393,7 @@ dependencies = [
"bs58 0.4.0",
"log",
"nym-bin-common",
"nym-config",
"nym-crypto",
"nym-mixnet-contract-common",
"nym-sphinx-addressing",
@@ -7399,7 +7401,12 @@ dependencies = [
"nym-sphinx-types",
"rand 0.7.3",
"semver 0.11.0",
"serde",
"serde_json",
"thiserror",
"tsify",
"wasm-bindgen",
"wasm-utils",
]
[[package]]
@@ -7530,6 +7537,7 @@ dependencies = [
"log",
"nym-task",
"tap",
"thiserror",
"tokio",
]
+14 -3
View File
@@ -21,6 +21,7 @@ use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_validator_client::QueryHttpRpcNyxdClient;
use std::error::Error;
use std::path::PathBuf;
use tokio::sync::watch::error::SendError;
pub use nym_sphinx::addressing::clients::Recipient;
@@ -34,11 +35,17 @@ pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates,
/// key filepaths, etc.
config: Config,
/// Optional path to a .json file containing standalone network details.
custom_mixnet: Option<PathBuf>,
}
impl SocketClient {
pub fn new(config: Config) -> Self {
SocketClient { config }
pub fn new(config: Config, custom_mixnet: Option<PathBuf>) -> Self {
SocketClient {
config,
custom_mixnet,
}
}
fn start_websocket_listener(
@@ -109,7 +116,11 @@ impl SocketClient {
let storage = self.initialise_storage().await?;
let base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
if let Some(custom_mixnet) = &self.custom_mixnet {
base_client = base_client.with_stored_topology(custom_mixnet)?;
}
Ok(base_client)
}
+29 -4
View File
@@ -15,12 +15,15 @@ use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::NymTopology;
use serde::Serialize;
use std::fmt::Display;
use std::net::IpAddr;
use std::path::PathBuf;
use std::{fs, io};
use tap::TapFallible;
@@ -49,7 +52,12 @@ pub(crate) struct Init {
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long, alias = "api_validators", value_delimiter = ',')]
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
@@ -65,6 +73,10 @@ pub(crate) struct Init {
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -130,7 +142,7 @@ fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
@@ -173,12 +185,25 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
let network_gateways = if let Some(hardcoded_topology) = args
.custom_mixnet
.map(NymTopology::new_from_file)
.transpose()?
{
// hardcoded_topology
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
current_gateways(&mut rng, &config.base.client.nym_api_urls).await?
};
let init_details = nym_client_core::init::setup_gateway_from(
gateway_setup,
&key_store,
&details_store,
register_gateway,
Some(&config.base.client.nym_api_urls),
Some(&network_gateways),
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
+2 -2
View File
@@ -84,8 +84,8 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
let bin_name = "nym-native-client";
match args.command {
Commands::Init(m) => init::execute(&m).await?,
Commands::Run(m) => run::execute(&m).await?,
Commands::Init(m) => init::execute(m).await?,
Commands::Run(m) => run::execute(m).await?,
Commands::BuildInfo(m) => build_info::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
+15 -3
View File
@@ -13,6 +13,7 @@ use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_crypto::asymmetric::identity;
use std::error::Error;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Args, Clone)]
pub(crate) struct Run {
@@ -25,7 +26,12 @@ pub(crate) struct Run {
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long, alias = "api_validators", value_delimiter = ',')]
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
@@ -46,6 +52,10 @@ pub(crate) struct Run {
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -95,7 +105,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
eprintln!("Starting client {}...", args.id);
let mut config = try_load_current_config(&args.id)?;
@@ -106,5 +116,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Syn
return Err(Box::new(ClientError::FailedLocalVersionCheck));
}
SocketClient::new(config).run_socket_forever().await
SocketClient::new(config, args.custom_mixnet)
.run_socket_forever()
.await
}
@@ -230,8 +230,7 @@ impl ServerResponse {
let error_kind = ErrorKind::try_from(b[1])?;
let message_len =
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
let message_len = u64::from_be_bytes(b[2..2 + size_of::<u64>()].try_into().unwrap());
let message = &b[2 + size_of::<u64>()..];
if message.len() as u64 != message_len {
return Err(error::Error::new(
+1
View File
@@ -16,6 +16,7 @@ serde_json = { workspace = true }
tap = "1.0.1"
thiserror = { workspace = true }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
rand = "0.7.3"
url = { workspace = true }
# internal
+23 -3
View File
@@ -14,11 +14,14 @@ use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::NymTopology;
use serde::Serialize;
use std::fmt::Display;
use std::path::PathBuf;
use std::{fs, io};
use tap::TapFallible;
@@ -68,6 +71,10 @@ pub(crate) struct Init {
#[clap(short, long)]
port: Option<u16>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -138,7 +145,7 @@ fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
@@ -185,12 +192,25 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
let network_gateways = if let Some(hardcoded_topology) = args
.custom_mixnet
.map(NymTopology::new_from_file)
.transpose()?
{
// hardcoded_topology
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
current_gateways(&mut rng, &config.core.base.client.nym_api_urls).await?
};
let init_details = nym_client_core::init::setup_gateway_from(
gateway_setup,
&key_store,
&details_store,
register_gateway,
Some(&config.core.base.client.nym_api_urls),
Some(&network_gateways),
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
+2 -2
View File
@@ -87,8 +87,8 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
let bin_name = "nym-socks5-client";
match args.command {
Commands::Init(m) => init::execute(&m).await?,
Commands::Run(m) => run::execute(&m).await?,
Commands::Init(m) => init::execute(m).await?,
Commands::Run(m) => run::execute(m).await?,
Commands::BuildInfo(m) => build_info::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
+11 -4
View File
@@ -15,6 +15,7 @@ use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::NymClient;
use nym_sphinx::addressing::clients::Recipient;
use std::path::PathBuf;
#[derive(Args, Clone)]
pub(crate) struct Run {
@@ -45,13 +46,17 @@ pub(crate) struct Run {
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the Nym APIs
#[clap(long, value_delimiter = ',')]
#[clap(long, value_delimiter = ',', group = "network")]
nym_apis: Option<Vec<url::Url>>,
/// Port for the socket to listen on
#[clap(short, long)]
port: Option<u16>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", group = "routing", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -62,7 +67,7 @@ pub(crate) struct Run {
no_cover: bool,
/// Set geo-aware mixnode selection when sending mixnet traffic, for experiments only.
#[clap(long, hide = true, value_parser = validate_country_group)]
#[clap(long, hide = true, value_parser = validate_country_group, group="routing")]
geo_routing: Option<CountryGroup>,
/// Enable medium mixnet traffic, for experiments only.
@@ -124,7 +129,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
eprintln!("Starting client {}...", args.id);
let mut config = try_load_current_config(&args.id)?;
@@ -138,5 +143,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error
let storage =
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
.await?;
NymClient::new(config.core, storage).run_forever().await
NymClient::new(config.core, storage, args.custom_mixnet)
.run_forever()
.await
}
+1 -1
View File
@@ -40,7 +40,7 @@ nym-gateway-requests = { path = "../../gateway/gateway-requests" }
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
nym-sphinx = { path = "../nymsphinx" }
nym-pemstore = { path = "../pemstore" }
nym-topology = { path = "../topology" }
nym-topology = { path = "../topology", features = ["serializable"] }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-task = { path = "../task" }
nym-credential-storage = { path = "../credential-storage" }
@@ -44,7 +44,9 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use std::path::Path;
use std::sync::Arc;
use url::Url;
@@ -178,11 +180,13 @@ where
}
}
#[must_use]
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
self
}
#[must_use]
pub fn with_topology_provider(
mut self,
provider: Box<dyn TopologyProvider + Send + Sync>,
@@ -191,6 +195,15 @@ where
self
}
pub fn with_stored_topology<P: AsRef<Path>>(
mut self,
file: P,
) -> Result<Self, ClientCoreError> {
self.custom_topology_provider =
Some(Box::new(HardcodedTopologyProvider::new_from_file(file)?));
Ok(self)
}
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded
fn mix_address(details: &InitialisationDetails) -> Recipient {
+1 -2
View File
@@ -242,8 +242,7 @@ impl PrivateKey {
/// Signs text with the provided Ed25519 private key, returning a base58 signature
pub fn sign_text(&self, text: &str) -> String {
let signature_bytes = self.sign(text.as_ref()).to_bytes();
let signature = bs58::encode(signature_bytes).into_string();
signature
bs58::encode(signature_bytes).into_string()
}
}
+11 -2
View File
@@ -26,6 +26,7 @@ use nym_sphinx::params::PacketType;
use nym_task::{TaskClient, TaskManager};
use std::error::Error;
use std::path::PathBuf;
pub mod config;
pub mod error;
@@ -57,6 +58,9 @@ pub struct NymClient<S> {
storage: S,
setup_method: GatewaySetup,
/// Optional path to a .json file containing standalone network details.
custom_mixnet: Option<PathBuf>,
}
impl<S> NymClient<S>
@@ -68,11 +72,12 @@ where
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
{
pub fn new(config: Config, storage: S) -> Self {
pub fn new(config: Config, storage: S, custom_mixnet: Option<PathBuf>) -> Self {
NymClient {
config,
storage,
setup_method: GatewaySetup::MustLoad,
custom_mixnet,
}
}
@@ -210,10 +215,14 @@ where
Some(default_query_dkg_client_from_config(&self.config.base))
};
let base_builder =
let mut base_builder =
BaseClientBuilder::new(&self.config.base, self.storage, dkg_query_client)
.with_gateway_setup(self.setup_method);
if let Some(custom_mixnet) = &self.custom_mixnet {
base_builder = base_builder.with_stored_topology(custom_mixnet)?;
}
let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address;
+17 -1
View File
@@ -19,6 +19,14 @@ thiserror = "1.0.37"
async-trait = { workspace = true, optional = true }
semver = "0.11"
# 'serializable' feature
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = { workspace = true, optional = true }
# 'wasm-serde-types' feature
tsify = { workspace = true, features = ["js"], optional = true }
wasm-bindgen = { workspace = true, optional = true }
## internal
nym-crypto = { path = "../crypto", features = ["sphinx", "outfox"] }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
@@ -27,6 +35,14 @@ nym-sphinx-types = { path = "../nymsphinx/types", features = ["sphinx", "outfox"
nym-sphinx-routing = { path = "../nymsphinx/routing" }
nym-bin-common = { path = "../bin-common" }
# 'serializable' feature
nym-config = { path = "../config", optional = true }
# 'wasm-serde-types' feature
wasm-utils = { path = "../wasm/utils", default-features = false, optional = true }
[features]
default = ["provider-trait"]
provider-trait = ["async-trait"]
provider-trait = ["async-trait"]
wasm-serde-types = ["tsify", "wasm-bindgen", "wasm-utils"]
serializable = ["serde", "nym-config", "serde_json"]
+40
View File
@@ -17,6 +17,9 @@ use std::io;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::str::FromStr;
#[cfg(feature = "serializable")]
use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
pub mod error;
pub mod filter;
pub mod gateway;
@@ -26,6 +29,12 @@ pub mod random_route_provider;
#[cfg(feature = "provider-trait")]
pub mod provider_trait;
#[cfg(feature = "serializable")]
pub(crate) mod serde;
#[cfg(feature = "serializable")]
pub use crate::serde::{SerializableNymTopology, SerializableTopologyError};
#[cfg(feature = "provider-trait")]
pub use provider_trait::{HardcodedTopologyProvider, TopologyProvider};
@@ -110,6 +119,12 @@ impl NymTopology {
NymTopology { mixes, gateways }
}
#[cfg(feature = "serializable")]
pub fn new_from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
let file = std::fs::File::open(path)?;
serde_json::from_reader(file).map_err(Into::into)
}
pub fn from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
@@ -171,6 +186,10 @@ impl NymTopology {
&self.gateways
}
pub fn get_gateways(&self) -> Vec<gateway::Node> {
self.gateways.clone()
}
pub fn get_gateway(&self, gateway_identity: &NodeIdentity) -> Option<&gateway::Node> {
self.gateways
.iter()
@@ -350,6 +369,27 @@ impl NymTopology {
}
}
#[cfg(feature = "serializable")]
impl Serialize for NymTopology {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
crate::serde::SerializableNymTopology::from(self.clone()).serialize(serializer)
}
}
#[cfg(feature = "serializable")]
impl<'de> Deserialize<'de> for NymTopology {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let serializable = crate::serde::SerializableNymTopology::deserialize(deserializer)?;
serializable.try_into().map_err(::serde::de::Error::custom)
}
}
pub fn nym_topology_from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
+5
View File
@@ -22,6 +22,11 @@ pub struct HardcodedTopologyProvider {
}
impl HardcodedTopologyProvider {
#[cfg(feature = "serializable")]
pub fn new_from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
NymTopology::new_from_file(path).map(Self::new)
}
pub fn new(topology: NymTopology) -> Self {
HardcodedTopologyProvider { topology }
}
+238
View File
@@ -0,0 +1,238 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::gateway::GatewayConversionError;
use crate::mix::MixnodeConversionError;
use crate::{gateway, mix, MixLayer, NymTopology};
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use nym_crypto::asymmetric::{encryption, identity};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
#[cfg(feature = "wasm-serde-types")]
use tsify::Tsify;
#[cfg(feature = "wasm-serde-types")]
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[cfg(feature = "wasm-serde-types")]
use wasm_utils::error::simple_js_error;
#[derive(Debug, Error)]
pub enum SerializableTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {msg}")]
MalformedMixnodeMap { msg: String },
#[error("The provided gateway list was malformed: {msg}")]
MalformedGatewayList { msg: String },
}
#[cfg(feature = "wasm-serde-types")]
impl From<SerializableTopologyError> for JsValue {
fn from(value: SerializableTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[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 SerializableNymTopology {
pub mixnodes: BTreeMap<MixLayer, Vec<SerializableMixNode>>,
pub gateways: Vec<SerializableGateway>,
}
impl TryFrom<SerializableNymTopology> for NymTopology {
type Error = SerializableTopologyError;
fn try_from(value: SerializableNymTopology) -> Result<Self, Self::Error> {
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in value.mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = value
.gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(NymTopology::new(converted_mixes, gateways))
}
}
impl From<NymTopology> for SerializableNymTopology {
fn from(value: NymTopology) -> Self {
SerializableNymTopology {
mixnodes: value
.mixes()
.iter()
.map(|(&l, nodes)| (l, nodes.iter().map(Into::into).collect()))
.collect(),
gateways: value.gateways().iter().map(Into::into).collect(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[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 SerializableMixNode {
// this is a `MixId` but due to typescript issue, we're using u32 directly.
#[serde(alias = "mix_id")]
pub mix_id: u32,
pub owner: String,
pub host: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "mix_port")]
pub mix_port: Option<u16>,
#[serde(alias = "identity_key")]
pub identity_key: String,
#[serde(alias = "sphinx_key")]
pub sphinx_key: String,
// this is a `MixLayer` but due to typescript issue, we're using u8 directly.
pub layer: u8,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub version: Option<String>,
}
impl TryFrom<SerializableMixNode> for mix::Node {
type Error = SerializableTopologyError;
fn try_from(value: SerializableMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, mix_port)?;
Ok(mix::Node {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: mix::Layer::try_from(value.layer)
.map_err(|_| SerializableTopologyError::InvalidMixLayer { value: value.layer })?,
version,
})
}
}
impl<'a> From<&'a mix::Node> for SerializableMixNode {
fn from(value: &'a mix::Node) -> Self {
SerializableMixNode {
mix_id: value.mix_id,
owner: value.owner.clone(),
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
layer: value.layer.into(),
version: Some(value.version.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[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 SerializableGateway {
pub owner: String,
pub host: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "mix_port")]
pub mix_port: Option<u16>,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "clients_port")]
pub clients_port: Option<u16>,
#[serde(alias = "identity_key")]
pub identity_key: String,
#[serde(alias = "sphinx_key")]
pub sphinx_key: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub version: Option<String>,
}
impl TryFrom<SerializableGateway> for gateway::Node {
type Error = SerializableTopologyError;
fn try_from(value: SerializableGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let clients_port = value.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::Node::extract_mix_host(&host, mix_port)?;
Ok(gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version,
})
}
}
impl<'a> From<&'a gateway::Node> for SerializableGateway {
fn from(value: &'a gateway::Node) -> Self {
SerializableGateway {
owner: value.owner.clone(),
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
clients_port: Some(value.clients_port),
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
version: Some(value.version.to_string()),
}
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ nym-gateway-client = { path = "../../client-libs/gateway-client", default-featur
nym-sphinx = { path = "../../nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"]}
nym-task = { path = "../../task" }
nym-topology = { path = "../../topology" }
nym-topology = { path = "../../topology", features = ["serializable", "wasm-serde-types"] }
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
wasm-utils = { path = "../utils" }
wasm-storage = { path = "../storage" }
+2 -3
View File
@@ -4,7 +4,6 @@
use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::WasmClientStorage;
use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
@@ -12,7 +11,7 @@ use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::{setup_gateway_from, GatewaySetup, InitialisationResult};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::{gateway, NymTopology};
use nym_topology::{gateway, NymTopology, SerializableNymTopology};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient;
use rand::thread_rng;
@@ -52,7 +51,7 @@ pub fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmCoreError>
pub async fn current_network_topology_async(
nym_api_url: String,
) -> Result<WasmNymTopology, WasmCoreError> {
) -> Result<SerializableNymTopology, WasmCoreError> {
let url: Url = match nym_api_url.parse() {
Ok(url) => url,
Err(source) => {
+14 -212
View File
@@ -2,55 +2,27 @@
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::GatewayEndpointConfig;
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use nym_crypto::asymmetric::{encryption, identity};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::{Layer, MixnodeConversionError};
use nym_topology::{gateway, mix, MixLayer, NymTopology};
pub use nym_topology::SerializableNymTopology;
use nym_topology::SerializableTopologyError;
use nym_validator_client::client::IdentityKeyRef;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
use tsify::Tsify;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use wasm_utils::console_log;
use wasm_utils::error::simple_js_error;
#[derive(Debug, Error)]
pub enum WasmTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
// redeclare this as a type alias for easy of use
pub type WasmTopologyError = SerializableTopologyError;
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
// helper trait to define extra functionality on the external type that we used to have here before
pub trait SerializableTopologyExt {
fn print(&self);
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {msg}")]
MalformedMixnodeMap { msg: String },
#[error("The provided gateway list was malformed: {msg}")]
MalformedGatewayList { msg: String },
}
impl From<WasmTopologyError> for JsValue {
fn from(value: WasmTopologyError) -> Self {
simple_js_error(value.to_string())
fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
}
fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool;
}
// serde helper, not intended to be used directly
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct WasmNymTopology {
mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>>,
gateways: Vec<WasmGateway>,
}
impl WasmNymTopology {
pub fn print(&self) {
impl SerializableTopologyExt for SerializableNymTopology {
fn print(&self) {
if !self.mixnodes.is_empty() {
console_log!("mixnodes:");
for (layer, nodes) in &self.mixnodes {
@@ -74,178 +46,8 @@ impl WasmNymTopology {
console_log!("NO GATEWAYS")
}
}
}
impl TryFrom<WasmNymTopology> for NymTopology {
type Error = WasmTopologyError;
fn try_from(value: WasmNymTopology) -> Result<Self, Self::Error> {
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in value.mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = value
.gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(NymTopology::new(converted_mixes, gateways))
}
}
impl WasmNymTopology {
pub fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
}
pub fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
self.gateways.iter().any(|g| g.identity_key == gateway_id)
}
}
impl From<NymTopology> for WasmNymTopology {
fn from(value: NymTopology) -> Self {
WasmNymTopology {
mixnodes: value
.mixes()
.iter()
.map(|(&l, nodes)| (l, nodes.iter().map(Into::into).collect()))
.collect(),
gateways: value.gateways().iter().map(Into::into).collect(),
}
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct WasmMixNode {
// this is a `MixId` but due to typescript issue, we're using u32 directly.
pub mix_id: u32,
pub owner: String,
pub host: String,
#[tsify(optional)]
pub mix_port: Option<u16>,
pub identity_key: String,
pub sphinx_key: String,
// this is a `MixLayer` but due to typescript issue, we're using u8 directly.
pub layer: u8,
#[tsify(optional)]
pub version: Option<String>,
}
impl TryFrom<WasmMixNode> for mix::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, mix_port)?;
Ok(mix::Node {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: Layer::try_from(value.layer)
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
version,
})
}
}
impl<'a> From<&'a mix::Node> for WasmMixNode {
fn from(value: &'a mix::Node) -> Self {
WasmMixNode {
mix_id: value.mix_id,
owner: value.owner.clone(),
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
layer: value.layer.into(),
version: Some(value.version.to_string()),
}
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct WasmGateway {
pub owner: String,
pub host: String,
#[tsify(optional)]
pub mix_port: Option<u16>,
#[tsify(optional)]
pub clients_port: Option<u16>,
pub identity_key: String,
pub sphinx_key: String,
#[tsify(optional)]
pub version: Option<String>,
}
impl TryFrom<WasmGateway> for gateway::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let clients_port = value.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::Node::extract_mix_host(&host, mix_port)?;
Ok(gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version,
})
}
}
impl<'a> From<&'a gateway::Node> for WasmGateway {
fn from(value: &'a gateway::Node) -> Self {
WasmGateway {
owner: value.owner.clone(),
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
clients_port: Some(value.clients_port),
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
version: Some(value.version.to_string()),
}
}
}
+1
View File
@@ -23,4 +23,5 @@ futures = "0.3.28"
log.workspace = true
nym-task = { path = "../task" }
tap.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "net"]}
+7
View File
@@ -0,0 +1,7 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum WgError {
#[error("unable to get tunnel")]
UnableToGetTunnel,
}
+3
View File
@@ -11,6 +11,9 @@ use tun::WireGuardTunnel;
use crate::event::Event;
pub use error::WgError;
mod error;
mod event;
mod tun;
+24 -13
View File
@@ -7,13 +7,14 @@ use boringtun::{
};
use bytes::Bytes;
use log::{debug, error, info, warn};
use tap::TapFallible;
use tokio::{
net::UdpSocket,
sync::{broadcast, mpsc},
time::timeout,
};
use crate::event::Event;
use crate::{event::Event, WgError};
const MAX_PACKET: usize = 65535;
@@ -99,7 +100,11 @@ impl WireGuardTunnel {
Some(packet) => {
info!("WireGuard tunnel received: {packet}");
match packet {
Event::WgPacket(data) => self.consume_wg(&data).await,
Event::WgPacket(data) => {
let _ = self.consume_wg(&data)
.await
.tap_err(|err| error!("WireGuard tunnel: consume_wg error: {err}"));
},
Event::IpPacket(data) => self.consume_eth(&data).await,
_ => {},
}
@@ -110,22 +115,24 @@ impl WireGuardTunnel {
},
},
_ = tokio::time::sleep(Duration::from_millis(250)) => {
self.update_wg_timers().await;
let _ = self.update_wg_timers()
.await
.map_err(|err| error!("WireGuard tunnel: update_wg_timers error: {err}"));
},
}
}
info!("WireGuard tunnel ({}): closed", self.addr);
}
async fn wg_tunnel_lock(&self) -> tokio::sync::MutexGuard<'_, Tunn> {
async fn wg_tunnel_lock(&self) -> Result<tokio::sync::MutexGuard<'_, Tunn>, WgError> {
timeout(Duration::from_millis(100), self.wg_tunnel.lock())
.await
.unwrap()
.map_err(|_| WgError::UnableToGetTunnel)
}
async fn consume_wg(&self, data: &[u8]) {
async fn consume_wg(&self, data: &[u8]) -> Result<(), WgError> {
let mut send_buf = [0u8; MAX_PACKET];
let mut peer = self.wg_tunnel_lock().await;
let mut peer = self.wg_tunnel_lock().await?;
match peer.decapsulate(None, data, &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
debug!("WireGuard: writing to network");
@@ -163,6 +170,7 @@ impl WireGuardTunnel {
error!("WireGuard: decapsulate error: {err:?}");
}
}
Ok(())
}
async fn consume_eth(&self, _data: &Bytes) {
@@ -170,11 +178,12 @@ impl WireGuardTunnel {
todo!();
}
async fn update_wg_timers(&mut self) {
async fn update_wg_timers(&mut self) -> Result<(), WgError> {
let mut send_buf = [0u8; MAX_PACKET];
let mut tun = self.wg_tunnel_lock().await;
let mut tun = self.wg_tunnel_lock().await?;
let tun_result = tun.update_timers(&mut send_buf);
self.handle_routine_tun_result(tun_result).await;
Ok(())
}
#[async_recursion]
@@ -192,10 +201,12 @@ impl WireGuardTunnel {
TunnResult::Err(WireGuardError::ConnectionExpired) => {
warn!("Wireguard handshake has expired!");
let mut buf = vec![0u8; MAX_PACKET];
let result = self
.wg_tunnel_lock()
.await
.format_handshake_initiation(&mut buf[..], false);
let Ok(mut peer) = self.wg_tunnel_lock().await else {
warn!("Failed to lock WireGuard peer, closing tunnel");
self.close();
return;
};
peer.format_handshake_initiation(&mut buf[..], false);
self.handle_routine_tun_result(result).await
}
TunnResult::Err(err) => {
+1 -1
View File
@@ -1302,7 +1302,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract"
version = "1.5.0"
version = "1.5.1"
dependencies = [
"bs58",
"cosmwasm-derive",
@@ -1,6 +1,6 @@
{
"contract_name": "nym-mixnet-contract",
"contract_version": "1.5.0",
"contract_version": "1.5.1",
"idl_version": "1.0.0",
"instantiate": {
"$schema": "http://json-schema.org/draft-07/schema#",
+6 -1
View File
@@ -1,4 +1,9 @@
#!/bin/bash
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# this is a script called by the github CI and CD workflows to build all 3 docs projects
# and move them to /dist/ in the root of the monorepo. They are rsynced to various servers
# from there by subsequent workflow tasks.
+5
View File
@@ -1,4 +1,9 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# takes one manadatory arg and one optional arg: wallet release and minimum rust versions
# it then uses sed to bump them in the three book.toml files.
#
@@ -1,4 +1,4 @@
# Integration FAQ
# Integrations FAQ
On this page, you'll find links and frequently asked questions on how to get started on integrating your project with Nym's Mixnet and its blockchain, Nyx.
@@ -199,3 +199,4 @@ No, although we do recommend that apps that wish to integrate look into running
### How can I find out if an application is already supported by network requester services?
You can check the [default allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) file to see which application traffic is whitelisted by default. If the domain is present on that list, it means that existing [network requesters](https://nymtech.net/docs/nodes/network-requester.html) can be used to privacy-protect your application traffic. Simply use [NymConnect](../quickstart/nymconnect-gui.md) to connect to this service through the mixnet.
@@ -3,7 +3,7 @@ If you've already gone through the different [Quick Start](../quickstart/overvie
This section assumes you wish to integrate with Nym into your application code.
The [integrations FAQ](./faq.md) has a list of common questions regarding integrating with Nym and Nyx, as well as commonly required links. _This is a good place to start to get an overall idea of the tools and software avaliable to you_.
The [integrations FAQ](../faq/integrations-faq.md) has a list of common questions regarding integrating with Nym and Nyx, as well as commonly required links. _This is a good place to start to get an overall idea of the tools and software avaliable to you_.
If you wish to integrate with Nym to use the mixnet for application traffic, start with the [mixnet integration](./mixnet-integration.md) page.
@@ -5,7 +5,7 @@ If you want to integrate with Nym in order to send `NYM` tokens (for instance, i
Nyx is the blockchain supporting the Nym network, hosting both the `NYM` and `NYX` cryptocurrencies, the CosmWasm smart contracts keeping track of the network, and (coming soon) facilitating zk-Nym credential generation. It is built with the [Cosmos SDK](https://tendermint.com/sdk/).
### Interacting with the Nyx blockchain
Check out the integration options in the [Integration FAQ](./faq.md#how-can-i-use-json-rpc-methods-to-interact-with-the-nyx-blockchain).
Check out the integration options in the [Integration FAQ](../faq/integrations-faq.md#how-can-i-use-json-rpc-methods-to-interact-with-the-nyx-blockchain).
### Chain information and RPC endpoints
You can find most information required for integration in the [Cosmos Chain Registry](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json) and [Keplr Chain Registry](https://github.com/chainapsis/keplr-chain-registry/blob/main/cosmos/nyx.json) repositories.
+3 -2
View File
@@ -82,10 +82,11 @@ If you're integrating mixnet functionality into an existing app and want to inte
### Anonymous replies with SURBs
Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default.
The number of SURBs is set [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/src/mixnet/client.rs#L33):
The number of SURBs is set [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/src/mixnet/client.rs#L33).
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/src/mixnet/client.rs:34}}
{{#include ../../../../sdk/rust/nym-sdk/src/mixnet/client.rs:33}}
```
You can read more about how SURBs function under the hood [here](../architecture/traffic-flow.md#private-replies-using-surbs).
+1 -1
View File
@@ -47,7 +47,7 @@ There are multiple example projects in [`nym/sdk/typescript/examples/`](https://
The best place to start if you just want to quickly get a basic frontend up and running with which to experiment is `examples/plain-html`:
```typescript
{{#include ../../../../sdk/typescript/examples/chat-app/plain-html/src/index.ts}}
{{#include ../../../../sdk/typescript/examples/shared/index.ts}}
```
As you can see, all that is required to create an ephemeral keypair and connect to the mixnet is creating a client and then subscribing to the mixnet events coming down the websocket, and adding logic to deal with them.
+5 -1
View File
@@ -18,7 +18,11 @@
- [Nyx Validator Setup](./nodes/validator-setup.md)
- [Maintenance](./nodes/maintenance.md)
- [Troubleshooting](./nodes/troubleshooting.md)
- [FAQ](./faq.md)
# FAQ
- [Mix Nodes](./faq/mixnodes-faq.md)
- [Project Smoosh](./faq/smoosh-faq.md)
---
# Misc.
@@ -55,3 +55,4 @@ We understand that the early days of the Nyx blockchain will face possible vulne
### Why does Nym do many airdrops?
It is part of ensuring decentralisation - we need to avoid a handful of people having too much control over the token and market. Of course ideally people will stake the tokens and contribute to the project at this stage. We run surveys to better understand what people are doing with their tokens and what usability issues there are for staking. Any feedback is appreciated as it helps us improve all aspects of using the token and participating in the ecosystem.
@@ -0,0 +1,81 @@
# Project Smoosh - FAQ
> We aim on purpose to make minimal changes to reward scheme and software. We're just 'smooshing' together stuff we already debugged and know works.
> -- Harry Halpin, Nym CEO
<p>
This page refer to the changes which are planned to take place over Q3 and Q4 2023. As this is a transition period in the beginning (Q3 2023) the [Mix Nodes FAQ page](./mixnodes-faq.md) holds more answers to the current setup as project Smoosh refers to the eventual setup. As project Smoosh gets progressively implemented the answers on this page will become to be more relevant to the current state and eventually this FAQ page will be merged with the still relevant parts of the main Mix Nodes FAQ page.
</p>
If any questions are not answered or it's not clear for you in which stage project Smoosh is right now, please reach out in Node Operators [Matrix room](https://matrix.to/#/#operators:nymtech.chat).
## Overview
### What is project Smoosh?
As we shared in our blog post article [*What does it take to build the wolds most powerful VPN*](https://blog.nymtech.net/what-does-it-take-to-build-the-worlds-most-powerful-vpn-d351a76ec4e6), project Smoosh is:
> A nick-name by CTO Dave Hrycyszyn and Chief Scientist Claudia Diaz for the work they are currently doing to “smoosh” Nym nodes so that the same operator can serve alternately as mix node, gateway or VPN node. This requires careful calibration of the Nym token economics, for example, only nodes with the highest reputation for good quality service will be in the VPN set and have the chance to earn higher rewards.
> By simplifying the components, adding VPN features and supporting new node operators, the aim is to widen the geographical coverage of nodes and have significant redundancy, meaning plenty of operators to be able to meet demand. This requires strong token economic incentives as well as training and support for new node operators.
## Technical Questions
### What are the changes?
Project smoosh will have three steps:
1. Combine the `gateway` and `network-requester`.
2. Combine all the nodes in the Nym Mixnet into one binary, that is `mixnode`, `gateway` (entry and exit) and `network-requester`.
3. Make a selection button (command/argument/flag) for operators to choose whether they want their node to provide all or just some of the functions nodes have in the Nym Mixnet. Not everyone will be able/want to run an exit `gateway` for example.
These three steps will be staggered over time - period of several months, and will be implemented one by one with enough time to take in feedback and fix bugs in between.
Generally, the software will be the same, just instead of multiple binaries, there will be one Nym Mixnet node binary. Delegations will remain on as they are now, per our token economics (staking, saturation etc)
### What is the change from allow list to deny list?
The operators running `gateways` would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays. The main change will be to expand the original short allow list to a more permissive setup. An exit policy will constrain the hosts that the users of the Nym VPN and Mixnet can connect to. This will be done in an effort to protect the operators, as Gateways will act both as SOCKS5 Network Requesters, and exit nodes for IP traffic from Nym VPN and Mixnet clients.
### Can I run a mix node only?
Yes, to run a mix node only is an option. However it will be less rewarded as nodes providing option for `gateway` - meaning the *new smooshed gateway* (previously `gateway` and `network requester`) - due to the work and risk the operators have in comparison to running a `mixnode` only.
## Token Economics & Rewards
### What are the incentives for the node operator?
In the original setup there were no incentives to run a `network-requester`. After the transition all the users will buy multiple tickets of zkNyms credentials and use those as [anonymous e-cash](https://arxiv.org/abs/2303.08221) to pay for their data traffic (`[Nym API](https://github.com/nymtech/nym/tree/master/nym-api)` will do the do cryptographical checks to prevent double-spending). All collected fees get distributed to all active nodes proportionally to their work by the end of each epoch.
### How does this change the token economics?
The token economics will stay the same as they are, same goes for the reward algorithm. In practice the distribution of rewards will benefit more the operators who run open gateways.
### How are the rewards distributed?
As each operator can choose what roles their nodes provide, the nodes which work as open gateways will have higher rewards because they are the most important to keep up and stable. Besides that the operators of gateways may be exposed to more complication and possible legal risks.
The nodes which are initialized to run as mix nodes and gateways will be chosen to be on top of the active set before the ones working only as a mix node.
We are considering to turn off the rewards for non-open gateways to incentivize operators to run the open ones. Mix nodes on 'standby' will not be rewarded (as they are not being used).
The more roles an operator will allow their node to provide the bigger reward ratio which will have huge performance benefits for the end-users.
### How will be the staking and inflation after project Smoosh?
We must run tests to see how many users pay. We may need to keep inflation on if not enough people pay to keep high quality gateways on in the early stage of the transition. That would mean keeping staking on for gateways. Staking will always be on for mix nodes.
### When project smooth will be launched, it would be the mixmining pool that will pay for the gateway rewards based on amount of traffic routed ?
Yes, the same pool. Nym's aim is to do minimal modifications. The only real modification on the smart contract side will be to get into top X of 'active set' operators will need to have open gateway function enabled.
### What does this mean for the current delegators?
From an operator standpoint, it shall just be a standard Nym upgrade, a new option to run the gateway software on your node. Delegators should not have to re-delegate.
## Legal Questions
### Are there any legal concerns for the operators?
So far the general line is running a gateway is not illegal (unless you are in Iran, China, and a few other places) and due to encryption/mixing less risky than running a normal VPN node. For mix nodes, its very safe as they have no idea what packets they are mixing.
There are several legal questions regarding to this and we would like to ask you to fill this [short survey](https://nymtech.typeform.com/exitnode).
We'll have a thorough legal analysis out before hand and various resources from and for the community. <!-- which we started to gather in the [Community Legal Forum](../legal/legal-forum.md). Uncomment when the legal forum get's merged -->
+1 -1
View File
@@ -22,5 +22,5 @@ If you want to dive deeper into Nym's architecture, clients, nodes, and SDK exam
**Maintenance, troubleshooting and FAQ**
* [Maintenance](./nodes/maintenance.md)
* [Troubleshooting](./nodes/troubleshooting.md)
* [FAQ](./faq.md)
* [FAQ](./faq/mixnodes-faq.md)
+6 -1
View File
@@ -1,4 +1,9 @@
#!/bin/bash
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# this is a script called by the github CI and CD workflows to post process CSS/image/href links for serving
# several mdbooks from a subdirectory
+4 -1
View File
@@ -4026,7 +4026,7 @@ dependencies = [
[[package]]
name = "nym-connect"
version = "1.1.20"
version = "1.1.21"
dependencies = [
"anyhow",
"bip39",
@@ -4604,6 +4604,7 @@ dependencies = [
"bs58 0.4.0",
"log",
"nym-bin-common",
"nym-config",
"nym-crypto",
"nym-mixnet-contract-common",
"nym-sphinx-addressing",
@@ -4611,6 +4612,8 @@ dependencies = [
"nym-sphinx-types",
"rand 0.7.3",
"semver 0.11.0",
"serde",
"serde_json",
"thiserror",
]
+1 -1
View File
@@ -113,7 +113,7 @@ pub async fn start_nym_socks5_client(
let result = tokio::runtime::Runtime::new()
.expect("Failed to create runtime for SOCKS5 client")
.block_on(async move {
let socks5_client = Socks5NymClient::new(config.core, storage);
let socks5_client = Socks5NymClient::new(config.core, storage, None);
socks5_client
.run_and_listen(socks5_ctrl_rx, socks5_status_tx)
+6
View File
@@ -2,6 +2,12 @@
## [Unreleased]
## [v1.2.8] (2023-08-23)
- [hotfix]: don't assign invalid fields when crossing the JS boundary ([#3805])
[#3805]: https://github.com/nymtech/nym/pull/3805
## [1.2.7] (2023-08-17)
- release due to schema changes in the contract
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-wallet-app",
"version": "1.2.7",
"version": "1.2.8",
"main": "index.js",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym_wallet"
version = "1.2.7"
version = "1.2.8"
description = "Nym Native Wallet"
authors = ["Nym Technologies SA"]
license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "1.2.7"
"version": "1.2.8"
},
"build": {
"distDir": "../dist",
+2 -3
View File
@@ -31,9 +31,8 @@
"build:playground": "lerna run --scope @nymproject/react storybook:build --stream",
"build:ci": "yarn build && run-p build:react-example build:playground && yarn build:ci:collect-artifacts",
"build:ci:collect-artifacts": "mkdir -p ts-packages/dist && mv ts-packages/react-components/storybook-static ts-packages/dist/storybook && mv ts-packages/react-webpack-with-theme-example/dist ts-packages/dist/example",
"docs:prod:build": "run-p docs:prod:build:ws docs:prod:build:ts:sdk",
"docs:prod:build": "run-s docs:prod:build:ws",
"docs:prod:build:ws": "lerna run docs:prod:build --stream",
"docs:prod:build:ts:sdk": "cd sdk/typescript/docs && npm i && npm run docs:prod:build",
"sdk:build": "./sdk/typescript/scripts/build-prod-sdk.sh",
"sdk:publish": "./sdk/typescript/scripts/publish.sh",
"lint": "lerna run lint --stream",
@@ -48,4 +47,4 @@
"@npmcli/node-gyp": "^3.0.0",
"node-gyp": "^9.3.1"
}
}
}
+1 -1
View File
@@ -264,7 +264,7 @@ where
let config = load_or_generate_base_config(storage_dir, client_id, service_provider).await?;
let storage = MobileClientStorage::new(&config);
let socks5_client = Socks5NymClient::new(config.core, storage)
let socks5_client = Socks5NymClient::new(config.core, storage, None)
.with_gateway_setup(GatewaySetup::New { by_latency: false });
eprintln!("starting the socks5 client");
+5
View File
@@ -1 +1,6 @@
.next
node_modules
out
# the lock file will break Vercel because it may get committed from a machine with a different build architecture
package-lock.json
@@ -0,0 +1,190 @@
```ts copy filename="CosmosKitExample.tsx"
import React, { FC } from 'react';
import { ChainProvider, useChain } from '@cosmos-kit/react';
import { assets, chains } from 'chain-registry';
import { wallets as keplr } from '@cosmos-kit/keplr';
import { wallets as ledger } from '@cosmos-kit/ledger';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { Alert, AlertTitle } from '@mui/material';
import { Wallet } from '@cosmos-kit/core';
import { CosmosKitLedger } from './ledger';
import { CosmosKitSign } from './sign';
const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => {
const assetsFixedUp = React.useMemo(() => {
const nyx = assets.find((a) => a.chain_name === 'nyx');
if (nyx) {
const nyxCoin = nyx.assets.find((a) => a.name === 'nyx');
if (nyxCoin) {
nyxCoin.coingecko_id = 'nyx';
}
nyx.assets = nyx.assets.reverse();
}
return assets;
}, [assets]);
const chainsFixedUp = React.useMemo(() => {
const nyx = chains.find((c) => c.chain_id === 'nyx');
if (nyx) {
if (!nyx.staking) {
nyx.staking = {
staking_tokens: [{ denom: 'unyx' }],
lock_duration: {
blocks: 10000,
},
};
}
}
return chains;
}, [chains]);
return (
<ChainProvider
chains={chainsFixedUp}
assetLists={assetsFixedUp}
wallets={[...ledger, ...keplr]}
signerOptions={{
preferredSignType: () => 'amino',
}}
>
{children}
</ChainProvider>
);
};
function walletRejectMessageOrError(wallet?: Wallet, error?: React.ReactNode) {
if (!wallet) {
if (!error) {
return undefined;
}
return error;
}
if (typeof wallet.rejectMessage === 'string') {
return wallet.rejectMessage;
}
return wallet.rejectMessage.source;
}
const Wrapper: FC<{
children?: React.ReactNode;
wallet?: Wallet;
header?: React.ReactNode;
error?: React.ReactNode;
disconnect: () => Promise<void>;
}> = ({ wallet, disconnect, error, header, children }) => {
if (error) {
return (
<Box mt={4} mb={2}>
<Alert severity="error">
{wallet && <AlertTitle>Failed to connect to {wallet.prettyName}</AlertTitle>}
{wallet && walletRejectMessageOrError(wallet)}
</Alert>
<Box mt={2}>
<Button variant="outlined" onClick={disconnect}>
Retry
</Button>
</Box>
</Box>
);
}
return (
<Box mt={4} mb={2}>
<Box display="flex" justifyContent="space-between">
{header && header}
<Button variant="outlined" onClick={disconnect}>
Disconnect
</Button>
</Box>
{children}
</Box>
);
};
const CosmosKitInner = () => {
const {
wallet,
address,
connect,
disconnect,
isWalletConnecting,
isWalletDisconnected,
isWalletError,
isWalletNotExist,
isWalletRejected,
} = useChain('nyx');
if (isWalletError) {
return <Wrapper wallet={wallet} error="Oh no! Something went wrong." disconnect={disconnect} />;
}
if (isWalletNotExist) {
return <Wrapper wallet={wallet} error="Oh no! Could not connect to that wallet." disconnect={disconnect} />;
}
if (isWalletRejected) {
return (
<Wrapper
wallet={wallet}
error="Oh no! Did you authorize the connection to your wallet?"
disconnect={disconnect}
/>
);
}
if (isWalletDisconnected) {
return (
<Button variant="outlined" onClick={connect} sx={{ mt: 4 }}>
Connect your wallet
</Button>
);
}
if (isWalletConnecting) {
return <CircularProgress />;
}
// Ledger Hardware Wallet
if (wallet.mode === 'ledger') {
return (
<Wrapper
header={
<Box>
<strong>Connected to {wallet.prettyName}</strong>
<Typography>
Address: <code>{address}</code>{' '}
</Typography>
</Box>
}
disconnect={disconnect}
>
<CosmosKitLedger />
</Wrapper>
);
}
// Extension or Wallet Connect
return (
<Wrapper
header={
<Box>
<strong>Connected to {wallet.prettyName}</strong>
<Typography>
Address: <code>{address}</code>{' '}
</Typography>
</Box>
}
disconnect={disconnect}
>
<CosmosKitSign />
</Wrapper>
);
};
export const CosmosKit = () => (
<CosmosKitSetup>
<CosmosKitInner />
</CosmosKitSetup>
);
```
@@ -0,0 +1,80 @@
```ts copy filename="mixFetchExample.tsx"
import React, { useState } from 'react';
import CircularProgress from '@mui/material/CircularProgress';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { mixFetch } from '@nymproject/mix-fetch-full-fat';
import Stack from '@mui/material/Stack';
import Paper from '@mui/material/Paper';
const defaultUrl = 'https://nymtech.net/favicon.svg';
const args = { mode: 'unsafe-ignore-cors' };
const mixFetchOptions = {
preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM',
preferredNetworkRequester:
'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW',
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
};
export const MixFetch = () => {
const [url, setUrl] = useState<string>(defaultUrl);
const [html, setHtml] = useState<string>();
const [busy, setBusy] = useState<boolean>(false);
const handleFetch = async () => {
try {
setBusy(true);
setHtml(undefined);
const response = await mixFetch(url, args, mixFetchOptions);
console.log(response);
const resHtml = await response.text();
setHtml(resHtml);
} catch (err) {
console.log(err);
} finally {
setBusy(false);
}
};
return (
<div style={{ marginTop: '1rem' }}>
<Stack direction="row">
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
/>
<Button variant="outlined" disabled={busy} sx={{ marginLeft: '1rem' }} onClick={handleFetch}>
Fetch
</Button>
</Stack>
{busy && (
<Box mt={4}>
<CircularProgress />
</Box>
)}
{html && (
<>
<Box mt={4}>
<strong>Response</strong>
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
</Paper>
</>
)}
</div>
);
};
```
@@ -0,0 +1,54 @@
```ts copy filename="MixnodeContractQueryExample.ts"
import { useEffect, useState } from "react";
import { contracts } from "@nymproject/contract-clients";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { settings } from "./client";
import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
const getClient = async () => {
const cosmWasmClient = await SigningCosmWasmClient.connect(settings.url);
const client = new contracts.Mixnet.MixnetQueryClient(
cosmWasmClient,
settings.mixnetContractAddress
);
return client;
};
export const Mixnodes = () => {
const [mixnodes, setMixnodes] = useState<any>();
const getMixnodes = async () => {
const client = await getClient();
const { nodes } = await client.getMixNodesDetailed({});
setMixnodes(nodes);
};
useEffect(() => {
getMixnodes();
}, []);
if (!mixnodes) {
return (
<Box sx={{ display: "flex" }}>
<CircularProgress />
</Box>
);
}
return (
<div style={{ marginTop: "1rem" }}>
{mixnodes?.length &&
mixnodes.map((mixnode: any) => (
<Box className="codeBox" key={mixnode.bond_information.mix_id}>
<span
style={{ marginRight: "1rem" }}
>{`id: ${mixnode.bond_information.mix_id}`}</span>
<span>{`owner: ${mixnode.bond_information.owner}`}</span>
</Box>
))}
</div>
);
};
```
@@ -0,0 +1,102 @@
```ts copy filename="MixnetWASMClientExample.tsx"
import React, { useEffect, useState } from 'react';
import { createNymMixnetClient, NymMixnetClient, Payload } from '@nymproject/sdk-full-fat';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
const nymApiUrl = 'https://validator.nymtech.net/api';
export const Traffic = () => {
const [nym, setNym] = useState<NymMixnetClient>();
const [selfAddress, setSelfAddress] = useState<string>();
const [recipient, setRecipient] = useState<string>();
const [payload, setPayload] = useState<Payload>();
const [receivedMessage, setReceivedMessage] = useState<string>();
const init = async () => {
const client = await createNymMixnetClient();
setNym(client);
await client?.client.start({
clientId: crypto.randomUUID(),
nymApiUrl,
});
client?.events.subscribeToConnected((e) => {
const { address } = e.args;
setSelfAddress(address);
});
client?.events.subscribeToLoaded((e) => {
console.log('Client ready: ', e.args);
});
client?.events.subscribeToTextMessageReceivedEvent((e) => {
console.log(e.args.payload);
setReceivedMessage(e.args.payload);
});
};
const stop = async () => {
await nym?.client.stop();
};
const send = () => nym.client.send({ payload, recipient });
useEffect(() => {
init();
return () => {
stop();
};
}, []);
if (!nym || !selfAddress) {
return (
<Box sx={{ display: 'flex' }}>
<CircularProgress />
</Box>
);
}
return (
<Box padding={3}>
<Paper style={{ marginTop: '1rem', padding: '2rem' }}>
<Stack spacing={3}>
<Typography variant="body1">My self address is:</Typography>
<Typography variant="body1">{selfAddress || 'loading'}</Typography>
<Typography variant="h5">Communication through the Mixnet</Typography>
<TextField
type="text"
placeholder="Recipient Address"
onChange={(e) => setRecipient(e.target.value)}
size="small"
/>
<TextField
type="text"
placeholder="Message to send"
multiline
rows={4}
onChange={(e) => setPayload({ message: e.target.value, mimeType: 'text/plain' })}
size="small"
/>
<Button variant="outlined" onClick={() => send()} disabled={!payload || !recipient} sx={{width: 'fit-content'}}>
Send
</Button>
</Stack>
{receivedMessage && (
<Stack spacing={3} style={{ marginTop: '1rem' }}>
<Typography variant="h5">Message Received!</Typography>
<Typography fontFamily="monospace">{receivedMessage}</Typography>
</Stack>
)}
</Paper>
</Box>
);
};
```
@@ -0,0 +1,384 @@
```ts copy filename="WalletSigningClientExample.tsx"
import React, { useCallback, useEffect, useState } from 'react';
import { contracts } from '@nymproject/contract-clients';
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { Coin, GasPrice } from '@cosmjs/stargate';
import Button from '@mui/material/Button';
import Input from '@mui/material/Input';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import { TableBody, TableCell, TableHead, TableRow, TextField, Typography } from '@mui/material';
import Divider from '@mui/material/Divider';
import Table from '@mui/material/Table';
import LoadingButton from '@mui/lab/LoadingButton';
import SaveIcon from '@mui/icons-material/Save';
import { settings } from './client';
const signerAccount = async (mnemonic) => {
const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: 'n',
});
return signer;
};
const fetchSignerCosmosWasmClient = async (mnemonic) => {
const signer = await signerAccount(mnemonic);
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, {
gasPrice: GasPrice.fromString('0.025unym'),
});
return cosmWasmClient;
};
const fetchSignerClient = async (mnemonic) => {
const signer = await signerAccount(mnemonic);
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, {
gasPrice: GasPrice.fromString('0.025unym'),
});
/** create a mixnet contract client
* @param cosmWasmClient the client to use for signing and querying
* @param settings.address the bech32 address prefix (human readable part)
* @param settings.mixnetContractAddress the bech32 address prefix (human readable part)
* @returns the client in MixnetClient form
*/
const mixnetClient = new contracts.Mixnet.MixnetClient(
cosmWasmClient,
settings.address, // sender (that account of the signer)
settings.mixnetContractAddress, // contract address (different on mainnet, QA, etc)
);
return mixnetClient;
};
export const Wallet = () => {
const [mnemonic, setMnemonic] = useState<string>();
const [signerCosmosWasmClient, setSignerCosmosWasmClient] = useState<any>();
const [signerClient, setSignerClient] = useState<any>();
const [account, setAccount] = useState<string>();
const [accountLoading, setAccountLoading] = useState<boolean>(false);
const [clientLoading, setClientLoading] = useState<boolean>(false);
const [balance, setBalance] = useState<Coin>();
const [balanceLoading, setBalanceLoading] = useState<boolean>(false);
const [log, setLog] = useState<React.ReactNode[]>([]);
const [tokensToSend, setTokensToSend] = useState<string>();
const [sendingTokensLoader, setSendingTokensLoader] = useState<boolean>(false);
const [delegations, setDelegations] = useState<any>();
const [recipientAddress, setRecipientAddress] = useState<string>('');
const [delegationNodeId, setDelegationNodeId] = useState<string>();
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
const [delegationLoader, setDelegationLoader] = useState<boolean>(false);
const [undeledationLoader, setUndeledationLoader] = useState<boolean>(false);
const [withdrawLoading, setWithdrawLoading] = useState<boolean>(false);
const getBalance = useCallback(async () => {
setBalanceLoading(true);
try {
const newBalance = await signerCosmosWasmClient?.getBalance(account, 'unym');
setBalance(newBalance);
} catch (error) {
console.error(error);
}
setBalanceLoading(false);
}, [account, signerCosmosWasmClient]);
const getSignerAccount = async () => {
setAccountLoading(true);
try {
const signer = await signerAccount(mnemonic);
const accounts = await signer.getAccounts();
if (accounts[0]) {
setAccount(accounts[0].address);
}
} catch (error) {
console.error(error);
}
setAccountLoading(false);
};
const getClients = async () => {
setClientLoading(true);
try {
setSignerCosmosWasmClient(await fetchSignerCosmosWasmClient(mnemonic));
setSignerClient(await fetchSignerClient(mnemonic));
} catch (error) {
console.error(error);
}
setClientLoading(false);
};
const getDelegations = useCallback(async () => {
const newDelegations = await signerClient.getDelegatorDelegations({
delegator: settings.address,
});
setDelegations(newDelegations);
}, [signerClient]);
const connect = () => {
getSignerAccount();
getClients();
};
const doUndelegateAll = async () => {
if (!signerClient) {
return;
}
setUndeledationLoader(true);
try {
// eslint-disable-next-line no-restricted-syntax
for (const delegation of delegations.delegations) {
// eslint-disable-next-line no-await-in-loop
await signerClient.undelegateFromMixnode({ mixId: delegation.mix_id }, 'auto');
}
} catch (error) {
console.error(error);
}
setUndeledationLoader(false);
};
const doDelegate = async ({ mixId, amount }: { mixId: number; amount: number }) => {
if (!signerClient) {
return;
}
setDelegationLoader(true);
try {
const res = await signerClient.delegateToMixnode({ mixId }, 'auto', undefined, [
{ amount: `${amount}`, denom: 'unym' },
]);
console.log('res', res);
setLog((prev) => [
...prev,
<div key={JSON.stringify(res, null, 2)}>
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
<pre>{JSON.stringify(res, null, 2)}</pre>
</div>,
]);
} catch (error) {
console.error(error);
}
setDelegationLoader(false);
};
// End delegate
// Sending tokens
const doSendTokens = async () => {
const memo = 'test sending tokens';
setSendingTokensLoader(true);
try {
const res = await signerCosmosWasmClient.sendTokens(
account,
recipientAddress,
[{ amount: tokensToSend, denom: 'unym' }],
'auto',
memo,
);
setLog((prev) => [
...prev,
<div key={JSON.stringify(res, null, 2)}>
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
<pre>{JSON.stringify(res, null, 2)}</pre>
</div>,
]);
} catch (error) {
console.error(error);
}
setSendingTokensLoader(false);
};
// End send tokens
// Withdraw Rewards
const doWithdrawRewards = async () => {
const delegatorAddress = '';
const validatorAdress = '';
const memo = 'test sending tokens';
setWithdrawLoading(true);
try {
const res = await signerCosmosWasmClient.withdrawRewards(delegatorAddress, validatorAdress, 'auto', memo);
setLog((prev) => [
...prev,
<div key={JSON.stringify(res, null, 2)}>
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
<pre>{JSON.stringify(res, null, 2)}</pre>
</div>,
]);
} catch (error) {
console.error(error);
}
setWithdrawLoading(false);
};
useEffect(() => {
if (account && signerCosmosWasmClient) {
if (!balance) {
setBalanceLoading(true);
getBalance();
setBalanceLoading(false);
}
}
}, [account, signerCosmosWasmClient, balance, getBalance]);
useEffect(() => {
if (signerClient && !delegations) {
console.log('getDelegations');
getDelegations();
}
}, [signerClient, getDelegations, delegations]);
return (
<Box padding={3}>
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<Typography variant="h5" textAlign="center">
Basic Wallet
</Typography>
<Box padding={3}>
<Typography variant="h6">Your account</Typography>
<Box marginY={3}>
<Typography variant="body1" marginBottom={3}>
Enter the mnemonic
</Typography>
<TextField
type="text"
placeholder="mnemonic"
onChange={(e) => setMnemonic(e.target.value)}
fullWidth
multiline
maxRows={4}
sx={{ marginBottom: 3 }}
/>
<Button
variant="outlined"
onClick={() => connect()}
disabled={!mnemonic || accountLoading || clientLoading || balanceLoading}
>
{accountLoading || clientLoading ? 'Loading...' : !balanceLoading ? 'Connect' : 'Connected'}
</Button>
</Box>
{account && balance ? (
<Box>
<Typography variant="body1">Address: {account}</Typography>
<Typography variant="body1">
Balance: {balance?.amount} {balance?.denom}
</Typography>
</Box>
) : (
<Box>
<Typography variant="body1">Please, enter your nemonic to receive your account info</Typography>
</Box>
)}
</Box>
<Divider />
<Box padding={3}>
<Typography variant="h6">Send Tokens</Typography>
<Box marginTop={3} display="flex" flexDirection="column">
<TextField
type="text"
placeholder="Recipient Address"
onChange={(e) => setRecipientAddress(e.target.value)}
size="small"
/>
<Box marginY={3} display="flex" justifyContent="space-between">
<TextField
type="text"
placeholder="Amount"
onChange={(e) => setTokensToSend(e.target.value)}
size="small"
/>
<Button variant="outlined" onClick={() => doSendTokens()} disabled={sendingTokensLoader}>
{sendingTokensLoader ? 'Sending...' : 'SendTokens'}
</Button>
</Box>
</Box>
</Box>
<Divider />
<Box padding={3}>
<Typography variant="h6">Delegations</Typography>
<Box marginY={3}>
<Box marginY={3} display="flex" flexDirection="column">
<Typography marginBottom={3} variant="body1">
Make a delegation
</Typography>
<TextField
type="text"
placeholder="Mixnode ID"
onChange={(e) => setDelegationNodeId(e.target.value)}
size="small"
/>
<Box marginTop={3} display="flex" justifyContent="space-between">
<TextField
type="text"
placeholder="Amount"
onChange={(e) => setAmountToBeDelegated(e.target.value)}
size="small"
/>
<Button
variant="outlined"
onClick={() =>
doDelegate({ mixId: parseInt(delegationNodeId, 10), amount: parseInt(amountToBeDelegated, 10) })
}
disabled={delegationLoader}
>
{delegationLoader ? 'Delegation in process...' : 'Delegate'}
</Button>
</Box>
</Box>
</Box>
<Box marginTop={3}>
<Typography variant="body1">Your delegations</Typography>
<Box marginBottom={3} display="flex" flexDirection="column">
{!delegations?.delegations?.length ? (
<Typography>You do not have delegations</Typography>
) : (
<Box>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>MixId</TableCell>
<TableCell>Owner</TableCell>
<TableCell>Amount</TableCell>
<TableCell>Cumulative Reward Ratio</TableCell>
</TableRow>
</TableHead>
<TableBody>
{delegations?.delegations.map((delegation: any) => (
<TableRow key={delegation.mix_id}>
<TableCell>{delegation.mix_id}</TableCell>
<TableCell>{delegation.owner}</TableCell>
<TableCell>{delegation.amount.amount}</TableCell>
<TableCell>{delegation.cumulative_reward_ratio}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
)}
</Box>
{delegations && (
<Box marginBottom={3}>
<Button variant="outlined" onClick={() => doUndelegateAll()} disabled={undeledationLoader}>
{undeledationLoader ? 'Undelegating...' : 'Undelegate All'}
</Button>
</Box>
)}
<Box>
<Button variant="outlined" onClick={() => doWithdrawRewards()} disabled={withdrawLoading}>
{withdrawLoading ? 'Doing withdraw...' : 'Withdraw rewards'}
</Button>
</Box>
</Box>
</Box>
</Paper>
<Box marginTop={3}>
<Typography variant="h5">Transaction Logs:</Typography>
{log}
</Box>
</Box>
);
};
```
@@ -0,0 +1,15 @@
export const mainnetSettings = {
url: 'wss://rpc.nymtech.net:443',
mixnetContractAddress: 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr',
mnemonic: process.env.MAINNET_MNEMONIC,
address: 'n1c7y676pe3av76r5usala759xgj0yplmvngu8u8',
};
export const qaSettings = {
url: 'wss://sandbox-validator1.nymtech.net/',
mixnetContractAddress: 'n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav',
mnemonic: process.env.QA_MNEMONIC,
address: 'n13uryxldwdllpakevsmt6n0uyfn3kgr2wvj5dnf',
};
export const settings = qaSettings;
@@ -0,0 +1,28 @@
import { AminoMsg, makeSignDoc, serializeSignDoc } from '@cosmjs/amino';
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
export const getDoc = (address: string) => {
const chainId = 'nyx';
const msg: AminoMsg = {
type: '/cosmos.bank.v1beta1.MsgSend',
value: MsgSend.fromPartial({
fromAddress: address,
toAddress: 'n1nn8tghp94n8utsgyg3kfttlxm0exgjrsqkuwu9',
amount: [{ amount: '1000', denom: 'unym' }],
}),
};
const fee = {
amount: [{ amount: '2000', denom: 'ucosm' }],
gas: '180000', // 180k
};
const memo = 'Use your power wisely';
const accountNumber = 15;
const sequence = 16;
return makeSignDoc([msg], fee, chainId, memo, accountNumber, sequence);
};
export const aminoDoc = (address: string) => {
const signDoc = getDoc(address);
return serializeSignDoc(signDoc);
};
@@ -0,0 +1,188 @@
import React, { FC } from 'react';
import { ChainProvider, useChain } from '@cosmos-kit/react';
import { assets, chains } from 'chain-registry';
import { wallets as keplr } from '@cosmos-kit/keplr';
import { wallets as ledger } from '@cosmos-kit/ledger';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { Alert, AlertTitle } from '@mui/material';
import { Wallet } from '@cosmos-kit/core';
import { CosmosKitLedger } from './ledger';
import { CosmosKitSign } from './sign';
const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => {
const assetsFixedUp = React.useMemo(() => {
const nyx = assets.find((a) => a.chain_name === 'nyx');
if (nyx) {
const nyxCoin = nyx.assets.find((a) => a.name === 'nyx');
if (nyxCoin) {
nyxCoin.coingecko_id = 'nyx';
}
nyx.assets = nyx.assets.reverse();
}
return assets;
}, [assets]);
const chainsFixedUp = React.useMemo(() => {
const nyx = chains.find((c) => c.chain_id === 'nyx');
if (nyx) {
if (!nyx.staking) {
nyx.staking = {
staking_tokens: [{ denom: 'unyx' }],
lock_duration: {
blocks: 10000,
},
};
}
}
return chains;
}, [chains]);
return (
<ChainProvider
chains={chainsFixedUp}
assetLists={assetsFixedUp}
wallets={[...ledger, ...keplr]}
signerOptions={{
preferredSignType: () => 'amino',
}}
>
{children}
</ChainProvider>
);
};
function walletRejectMessageOrError(wallet?: Wallet, error?: React.ReactNode) {
if (!wallet) {
if (!error) {
return undefined;
}
return error;
}
if (typeof wallet.rejectMessage === 'string') {
return wallet.rejectMessage;
}
return wallet.rejectMessage.source;
}
const Wrapper: FC<{
children?: React.ReactNode;
wallet?: Wallet;
header?: React.ReactNode;
error?: React.ReactNode;
disconnect: () => Promise<void>;
}> = ({ wallet, disconnect, error, header, children }) => {
if (error) {
return (
<Box mt={4} mb={2}>
<Alert severity="error">
{wallet && <AlertTitle>Failed to connect to {wallet.prettyName}</AlertTitle>}
{wallet && walletRejectMessageOrError(wallet)}
</Alert>
<Box mt={2}>
<Button variant="outlined" onClick={disconnect}>
Retry
</Button>
</Box>
</Box>
);
}
return (
<Box mt={4} mb={2}>
<Box display="flex" justifyContent="space-between">
{header && header}
<Button variant="outlined" onClick={disconnect}>
Disconnect
</Button>
</Box>
{children}
</Box>
);
};
const CosmosKitInner = () => {
const {
wallet,
address,
connect,
disconnect,
isWalletConnecting,
isWalletDisconnected,
isWalletError,
isWalletNotExist,
isWalletRejected,
} = useChain('nyx');
if (isWalletError) {
return <Wrapper wallet={wallet} error="Oh no! Something went wrong." disconnect={disconnect} />;
}
if (isWalletNotExist) {
return <Wrapper wallet={wallet} error="Oh no! Could not connect to that wallet." disconnect={disconnect} />;
}
if (isWalletRejected) {
return (
<Wrapper
wallet={wallet}
error="Oh no! Did you authorize the connection to your wallet?"
disconnect={disconnect}
/>
);
}
if (isWalletDisconnected) {
return (
<Button variant="outlined" onClick={connect} sx={{ mt: 4 }}>
Connect your wallet
</Button>
);
}
if (isWalletConnecting) {
return <CircularProgress />;
}
// Ledger Hardware Wallet
if (wallet.mode === 'ledger') {
return (
<Wrapper
header={
<Box>
<strong>Connected to {wallet.prettyName}</strong>
<Typography>
Address: <code>{address}</code>{' '}
</Typography>
</Box>
}
disconnect={disconnect}
>
<CosmosKitLedger />
</Wrapper>
);
}
// Extension or Wallet Connect
return (
<Wrapper
header={
<Box>
<strong>Connected to {wallet.prettyName}</strong>
<Typography>
Address: <code>{address}</code>{' '}
</Typography>
</Box>
}
disconnect={disconnect}
>
<CosmosKitSign />
</Wrapper>
);
};
export const CosmosKit = () => (
<CosmosKitSetup>
<CosmosKitInner />
</CosmosKitSetup>
);
@@ -0,0 +1,74 @@
import React from 'react';
import { useChain, useWalletClient } from '@cosmos-kit/react';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import { pubkeyType } from '@cosmjs/amino';
import { toBase64 } from '@cosmjs/encoding';
import { Alert, AlertTitle, LinearProgress } from '@mui/material';
import { aminoDoc } from './data';
export const CosmosKitLedger = () => {
const { wallet, address } = useChain('nyx');
const { client } = useWalletClient(wallet?.name);
const [signResponse, setSignResponse] = React.useState<any>();
const [busy, setBusy] = React.useState<boolean>();
const sign = async () => {
setBusy(true);
const serialized = aminoDoc(address);
const account = await client.getAccount('nyx');
console.log('Accounts: ', account);
console.log('Info', await (client as any).client.getAppConfiguration());
const sigAmino = await (client as any).client.sign(account.username, serialized);
const sig = {
signature: toBase64(sigAmino.signature),
pub_key: {
type: pubkeyType.secp256k1,
value: toBase64(account.pubkey),
},
};
console.log('Sig', { sigAmino, sig });
setBusy(false);
return { signature: sig };
};
const handleSign = async () => {
setSignResponse(await sign());
};
if (busy) {
return (
<Box mt={4} mb={2}>
<LinearProgress color="success" />
<Alert severity="success">
<AlertTitle>Please approve on the Ledger</AlertTitle>
Follow the instructions on the Ledger to review the message
</Alert>
</Box>
);
}
return (
<>
{!signResponse && (
<Box mt={4} mb={2}>
<Box mb={2}>Click the button below to sign a fake request with your Ledger</Box>
<Button variant="outlined" onClick={handleSign}>
Click to sign
</Button>
</Box>
)}
{signResponse && (
<Box mt={2}>
<strong>Signature:</strong>
<Box sx={{ overflowX: 'auto' }}>
<pre>{JSON.stringify(signResponse?.signature, null, 2)}</pre>
</Box>
</Box>
)}
</>
);
};
@@ -0,0 +1,57 @@
import React from 'react';
import { useChain } from '@cosmos-kit/react';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import { Alert, AlertTitle, LinearProgress } from '@mui/material';
import { getDoc } from './data';
export const CosmosKitSign = () => {
const { address, getOfflineSignerAmino } = useChain('nyx');
const [signResponse, setSignResponse] = React.useState<any>();
const [busy, setBusy] = React.useState<boolean>();
const sign = async () => {
setBusy(true);
const doc = getDoc(address);
const tx = await getOfflineSignerAmino().signAmino(address, doc);
setBusy(false);
return tx;
};
const handleSign = async () => {
setSignResponse(await sign());
};
if (busy) {
return (
<Box mt={4} mb={2}>
<LinearProgress color="success" />
<Alert severity="success">
<AlertTitle>Please approve in your wallet</AlertTitle>
Review the message to sign
</Alert>
</Box>
);
}
return (
<>
{!signResponse && (
<Box mt={4} mb={2}>
<Box mb={2}>Click the button below to sign a fake request with your wallet</Box>
<Button variant="outlined" onClick={handleSign}>
Click to sign
</Button>
</Box>
)}
{signResponse && (
<Box mt={2}>
<strong>Signature:</strong>
<Box sx={{ overflowX: 'auto' }}>
<pre>{JSON.stringify(signResponse?.signature, null, 2)}</pre>
</Box>
</Box>
)}
</>
);
};
+22
View File
@@ -0,0 +1,22 @@
import React from 'react';
import Stack from '@mui/material/Stack';
const links = [
['Twitter', 'https://twitter.com/nymproject'],
['Telegram', 'https://t.me/nymchan'],
['Discord', 'https://discord.com/invite/nym'],
['GitHub', 'https://github.com/nymtech/nym'],
['Nym Wallet', 'https://nymtech.net/download/wallet'],
['Nym Explorer', 'https://explorer.nymtech.net/'],
['Nym Blog', 'https://nymtech.medium.com/'],
['Nym Shipyard', 'https://shipyard.nymtech.net/'],
];
export const Footer = () => (
<Stack direction="row" spacing={3}>
{links.map((link) => (
<a key={link[1]} href={link[1]}>
{link[0]}
</a>
))}
</Stack>
);
@@ -0,0 +1,78 @@
import React, { useState } from 'react';
import CircularProgress from '@mui/material/CircularProgress';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { mixFetch } from '@nymproject/mix-fetch-full-fat';
import Stack from '@mui/material/Stack';
import Paper from '@mui/material/Paper';
const defaultUrl = 'https://nymtech.net/favicon.svg';
const args = { mode: 'unsafe-ignore-cors' };
const mixFetchOptions = {
preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS
preferredNetworkRequester:
'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW',
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
};
export const MixFetch = () => {
const [url, setUrl] = useState<string>(defaultUrl);
const [html, setHtml] = useState<string>();
const [busy, setBusy] = useState<boolean>(false);
const handleFetch = async () => {
try {
setBusy(true);
setHtml(undefined);
const response = await mixFetch(url, args, mixFetchOptions);
console.log(response);
const resHtml = await response.text();
setHtml(resHtml);
} catch (err) {
console.log(err);
} finally {
setBusy(false);
}
};
return (
<div style={{ marginTop: '1rem' }}>
<Stack direction="row">
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
/>
<Button variant="outlined" disabled={busy} sx={{ marginLeft: '1rem' }} onClick={handleFetch}>
Fetch
</Button>
</Stack>
{busy && (
<Box mt={4}>
<CircularProgress />
</Box>
)}
{html && (
<>
<Box mt={4}>
<strong>Response</strong>
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
</Paper>
</>
)}
</div>
);
};
@@ -0,0 +1,79 @@
import React, { useState } from 'react';
import { contracts } from '@nymproject/contract-clients';
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import Table from '@mui/material/Table';
import { TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import { settings } from './client';
const getClient = async () => {
const cosmWasmClient = await SigningCosmWasmClient.connect(settings.url);
const client = new contracts.Mixnet.MixnetQueryClient(cosmWasmClient, settings.mixnetContractAddress);
return client;
};
export const Mixnodes = () => {
const [mixnodes, setMixnodes] = useState<any>();
const [busy, setBusy] = useState<boolean>(false);
const getMixnodes = async () => {
setBusy(true);
const client = await getClient();
const { nodes } = await client.getMixNodesDetailed({});
setMixnodes(nodes);
setBusy(false);
};
if (busy) {
return (
<Box pt={4}>
<Stack direction="row" spacing={2} alignItems="center">
<CircularProgress />
<Typography>Loading...</Typography>
</Stack>
</Box>
);
}
if (!mixnodes) {
return (
<Box pt={4}>
<Button variant="outlined" onClick={getMixnodes}>
Query for mixnodes
</Button>
</Box>
);
}
return (
<Box pt={4}>
{mixnodes?.length && (
<Table size="small">
<TableHead>
<TableRow>
<TableCell>MixId</TableCell>
<TableCell>Owner Account</TableCell>
<TableCell>Layer</TableCell>
<TableCell>Bonded at Block Height</TableCell>
</TableRow>
</TableHead>
<TableBody>
{mixnodes.map((mixnode: any) => (
<TableRow key={mixnode.bond_information.mix_id}>
<TableCell>{mixnode.bond_information.mix_id}</TableCell>
<TableCell>{mixnode.bond_information.owner}</TableCell>
<TableCell>{mixnode.bond_information.layer}</TableCell>
<TableCell>{mixnode.bond_information.bonding_height}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</Box>
);
};
+17
View File
@@ -0,0 +1,17 @@
import React, { FC } from 'react';
import { Chip, Link } from '@mui/material';
export const NPMLink: FC<{ packageName: string; kind: 'esm' | 'cjs'; preBundled?: boolean }> = ({
packageName,
kind,
preBundled,
}) => (
<Link
href={`https://www.npmjs.com/package/${packageName}`}
target="_blank"
sx={{ whiteSpace: 'nowrap', textDecoration: 'none' }}
>
{packageName} <Chip label={kind === 'cjs' ? 'CommonJS' : 'ESM'} size="small" />{' '}
{preBundled && <Chip label="pre-bundled" size="small" color="info" />}
</Link>
);
+112
View File
@@ -0,0 +1,112 @@
import React, { useEffect, useState } from 'react';
import { createNymMixnetClient, NymMixnetClient, Payload } from '@nymproject/sdk-full-fat';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
const nymApiUrl = 'https://validator.nymtech.net/api';
export const Traffic = () => {
const [nym, setNym] = useState<NymMixnetClient>();
const [selfAddress, setSelfAddress] = useState<string>();
const [recipient, setRecipient] = useState<string>();
const [payload, setPayload] = useState<Payload>();
const [receivedMessage, setReceivedMessage] = useState<string>();
const [buttonEnabled, setButtonEnabled] = useState<boolean>(false);
const init = async () => {
const client = await createNymMixnetClient();
setNym(client);
// start the client and connect to a gateway
await client?.client.start({
clientId: crypto.randomUUID(),
nymApiUrl,
});
// check when is connected and set the self address
client?.events.subscribeToConnected((e) => {
const { address } = e.args;
setSelfAddress(address);
});
// show whether the client is ready or not
client?.events.subscribeToLoaded((e) => {
console.log('Client ready: ', e.args);
});
// show message payload content when received
client?.events.subscribeToTextMessageReceivedEvent((e) => {
console.log(e.args.payload);
setReceivedMessage(e.args.payload);
});
};
const stop = async () => {
await nym?.client.stop();
};
const send = () => nym.client.send({ payload, recipient });
useEffect(() => {
init();
return () => {
stop();
};
}, []);
useEffect(() => {
if (recipient && payload) {
setButtonEnabled(true);
} else {
setButtonEnabled(false);
}
}, [recipient, payload]);
if (!nym || !selfAddress) {
return (
<Box sx={{ display: 'flex' }}>
<CircularProgress />
</Box>
);
}
return (
<Box padding={3}>
<Paper style={{ marginTop: '1rem', padding: '2rem' }}>
<Stack spacing={3}>
<Typography variant="body1">My self address is:</Typography>
<Typography variant="body1">{selfAddress || 'loading'}</Typography>
<Typography variant="h5">Communication through the Mixnet</Typography>
<TextField
type="text"
placeholder="Recipient Address"
onChange={(e) => setRecipient(e.target.value)}
size="small"
/>
<TextField
type="text"
placeholder="Message to send"
multiline
rows={4}
onChange={(e) => setPayload({ message: e.target.value, mimeType: 'text/plain' })}
size="small"
/>
<Button variant="outlined" onClick={() => send()} disabled={!buttonEnabled} sx={{ width: 'fit-content' }}>
Send
</Button>
</Stack>
{receivedMessage && (
<Stack spacing={3} style={{ marginTop: '1rem' }}>
<Typography variant="h5">Message Received!</Typography>
<Typography fontFamily="monospace">{receivedMessage}</Typography>
</Stack>
)}
</Paper>
</Box>
);
};
+387 -22
View File
@@ -1,27 +1,392 @@
import React, { FC } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { contracts } from '@nymproject/contract-clients';
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { Coin, GasPrice } from '@cosmjs/stargate';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import { TableBody, TableCell, TableHead, TableRow, TextField, Typography } from '@mui/material';
import Divider from '@mui/material/Divider';
import Table from '@mui/material/Table';
import { settings } from './client';
import { ChainProvider, useChainWallet } from '@cosmos-kit/react';
import { chains, assets } from 'chain-registry';
import { wallets } from '@cosmos-kit/keplr';
const signerAccount = async (mnemonic) => {
// create a wallet to sign transactions with the mnemonic
const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: 'n',
});
// Import this in your top-level route/layout
import '@interchain-ui/react/styles';
const WalletInner: FC = () => {
const chainContext = useChainWallet('nyx', 'keplr');
return <pre>{JSON.stringify(chainContext, null, 2)}</pre>;
return signer;
};
export const Wallet: FC = () => (
// TODO
const fetchSignerCosmosWasmClient = async (mnemonic: string) => {
const signer = await signerAccount(mnemonic);
<ChainProvider
chains={chains} // supported chains
assetLists={assets} // supported asset lists
wallets={wallets} // supported wallets
>
<div>This is a wallet</div>
<WalletInner />
</ChainProvider>
);
// create a signing client we don't need to set the gas price conversion for queries
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, {
gasPrice: GasPrice.fromString('0.025unym'),
});
return cosmWasmClient;
};
const fetchSignerClient = async (mnemonic) => {
const signer = await signerAccount(mnemonic);
// create a signing client we don't need to set the gas price conversion for queries
// if you want to connect without signer you'd write ".connect" and "url" as param
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(settings.url, signer, {
gasPrice: GasPrice.fromString('0.025unym'),
});
/** create a mixnet contract client
* @param cosmWasmClient the client to use for signing and querying
* @param settings.address the bech32 address prefix (human readable part)
* @param settings.mixnetContractAddress the bech32 address prefix (human readable part)
* @returns the client in MixnetClient form
*/
const mixnetClient = new contracts.Mixnet.MixnetClient(
cosmWasmClient,
settings.address, // sender (that account of the signer)
settings.mixnetContractAddress, // contract address (different on mainnet, QA, etc)
);
return mixnetClient;
};
export const Wallet = () => {
const [mnemonic, setMnemonic] = useState<string>();
const [signerCosmosWasmClient, setSignerCosmosWasmClient] = useState<any>();
const [signerClient, setSignerClient] = useState<any>();
const [account, setAccount] = useState<string>();
const [accountLoading, setAccountLoading] = useState<boolean>(false);
const [clientLoading, setClientLoading] = useState<boolean>(false);
const [balance, setBalance] = useState<Coin>();
const [balanceLoading, setBalanceLoading] = useState<boolean>(false);
const [log, setLog] = useState<React.ReactNode[]>([]);
const [tokensToSend, setTokensToSend] = useState<string>();
const [sendingTokensLoader, setSendingTokensLoader] = useState<boolean>(false);
const [delegations, setDelegations] = useState<any>();
const [recipientAddress, setRecipientAddress] = useState<string>('');
const [delegationNodeId, setDelegationNodeId] = useState<string>();
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
const [delegationLoader, setDelegationLoader] = useState<boolean>(false);
const [undeledationLoader, setUndeledationLoader] = useState<boolean>(false);
const [withdrawLoading, setWithdrawLoading] = useState<boolean>(false);
const [connectButtonText, setConnectButtonText] = useState<string>('Connect');
const getBalance = useCallback(async () => {
setBalanceLoading(true);
try {
const newBalance = await signerCosmosWasmClient?.getBalance(account, 'unym');
setBalance(newBalance);
} catch (error) {
console.error(error);
}
setBalanceLoading(false);
}, [account, signerCosmosWasmClient]);
const getSignerAccount = async () => {
setAccountLoading(true);
try {
const signer = await signerAccount(mnemonic);
const accounts = await signer.getAccounts();
if (accounts[0]) {
setAccount(accounts[0].address);
}
} catch (error) {
console.error(error);
}
setAccountLoading(false);
};
const getClients = async () => {
setClientLoading(true);
try {
setSignerCosmosWasmClient(await fetchSignerCosmosWasmClient(mnemonic));
setSignerClient(await fetchSignerClient(mnemonic));
} catch (error) {
console.error(error);
}
setClientLoading(false);
};
const getDelegations = useCallback(async () => {
const newDelegations = await signerClient.getDelegatorDelegations({
delegator: settings.address,
});
setDelegations(newDelegations);
}, [signerClient]);
const connect = () => {
getSignerAccount();
getClients();
};
const doUndelegateAll = async () => {
if (!signerClient) {
return;
}
setUndeledationLoader(true);
try {
// eslint-disable-next-line no-restricted-syntax
for (const delegation of delegations.delegations) {
// eslint-disable-next-line no-await-in-loop
await signerClient.undelegateFromMixnode({ mixId: delegation.mix_id }, 'auto');
}
} catch (error) {
console.error(error);
}
setUndeledationLoader(false);
};
const doDelegate = async ({ mixId, amount }: { mixId: number; amount: number }) => {
if (!signerClient) {
return;
}
setDelegationLoader(true);
try {
const res = await signerClient.delegateToMixnode({ mixId }, 'auto', undefined, [
{ amount: `${amount}`, denom: 'unym' },
]);
console.log('res', res);
setLog((prev) => [
...prev,
<div key={JSON.stringify(res, null, 2)}>
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
<pre>{JSON.stringify(res, null, 2)}</pre>
</div>,
]);
} catch (error) {
console.error(error);
}
setDelegationLoader(false);
};
// End delegate
// Sending tokens
const doSendTokens = async () => {
const memo = 'test sending tokens';
setSendingTokensLoader(true);
try {
const res = await signerCosmosWasmClient.sendTokens(
account,
recipientAddress,
[{ amount: tokensToSend, denom: 'unym' }],
'auto',
memo,
);
setLog((prev) => [
...prev,
<div key={JSON.stringify(res, null, 2)}>
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
<pre>{JSON.stringify(res, null, 2)}</pre>
</div>,
]);
} catch (error) {
console.error(error);
}
setSendingTokensLoader(false);
};
// End send tokens
// Withdraw Rewards
const doWithdrawRewards = async () => {
const delegatorAddress = '';
const validatorAdress = '';
const memo = 'test sending tokens';
setWithdrawLoading(true);
try {
const res = await signerCosmosWasmClient.withdrawRewards(delegatorAddress, validatorAdress, 'auto', memo);
setLog((prev) => [
...prev,
<div key={JSON.stringify(res, null, 2)}>
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
<pre>{JSON.stringify(res, null, 2)}</pre>
</div>,
]);
} catch (error) {
console.error(error);
}
setWithdrawLoading(false);
};
useEffect(() => {
if (account && signerCosmosWasmClient) {
if (!balance) {
setBalanceLoading(true);
getBalance();
setBalanceLoading(false);
}
}
}, [account, signerCosmosWasmClient, balance, getBalance]);
useEffect(() => {
if (signerClient && !delegations) {
console.log('getDelegations');
getDelegations();
}
}, [signerClient, getDelegations, delegations]);
useEffect(() => {
if (accountLoading || clientLoading || balanceLoading) {
setConnectButtonText('Loading...');
} else if (balance) {
setConnectButtonText('Connected');
}
setConnectButtonText('Connect');
}, [accountLoading, clientLoading, balanceLoading]);
return (
<Box padding={3}>
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<Typography variant="h5" textAlign="center">
Basic Wallet
</Typography>
<Box padding={3}>
<Typography variant="h6">Your account</Typography>
<Box marginY={3}>
<Typography variant="body1" marginBottom={3}>
Enter the mnemonic
</Typography>
<TextField
type="text"
placeholder="mnemonic"
onChange={(e) => setMnemonic(e.target.value)}
fullWidth
multiline
maxRows={4}
sx={{ marginBottom: 3 }}
/>
<Button
variant="outlined"
onClick={() => connect()}
disabled={!mnemonic || accountLoading || clientLoading || balanceLoading}
>
{connectButtonText}
</Button>
</Box>
{account && balance ? (
<Box>
<Typography variant="body1">Address: {account}</Typography>
<Typography variant="body1">
Balance: {balance?.amount} {balance?.denom}
</Typography>
</Box>
) : (
<Box>
<Typography variant="body1">Please, enter your nemonic to receive your account info</Typography>
</Box>
)}
</Box>
<Divider />
<Box padding={3}>
<Typography variant="h6">Send Tokens</Typography>
<Box marginTop={3} display="flex" flexDirection="column">
<TextField
type="text"
placeholder="Recipient Address"
onChange={(e) => setRecipientAddress(e.target.value)}
size="small"
/>
<Box marginY={3} display="flex" justifyContent="space-between">
<TextField
type="text"
placeholder="Amount"
onChange={(e) => setTokensToSend(e.target.value)}
size="small"
/>
<Button variant="outlined" onClick={() => doSendTokens()} disabled={sendingTokensLoader}>
{sendingTokensLoader ? 'Sending...' : 'SendTokens'}
</Button>
</Box>
</Box>
</Box>
<Divider />
<Box padding={3}>
<Typography variant="h6">Delegations</Typography>
<Box marginY={3}>
<Box marginY={3} display="flex" flexDirection="column">
<Typography marginBottom={3} variant="body1">
Make a delegation
</Typography>
<TextField
type="text"
placeholder="Mixnode ID"
onChange={(e) => setDelegationNodeId(e.target.value)}
size="small"
/>
<Box marginTop={3} display="flex" justifyContent="space-between">
<TextField
type="text"
placeholder="Amount"
onChange={(e) => setAmountToBeDelegated(e.target.value)}
size="small"
/>
<Button
variant="outlined"
onClick={() =>
doDelegate({ mixId: parseInt(delegationNodeId, 10), amount: parseInt(amountToBeDelegated, 10) })
}
disabled={delegationLoader}
>
{delegationLoader ? 'Delegation in process...' : 'Delegate'}
</Button>
</Box>
</Box>
</Box>
<Box marginTop={3}>
<Typography variant="body1">Your delegations</Typography>
<Box marginBottom={3} display="flex" flexDirection="column">
{!delegations?.delegations?.length ? (
<Typography>You do not have delegations</Typography>
) : (
<Box>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>MixId</TableCell>
<TableCell>Owner</TableCell>
<TableCell>Amount</TableCell>
<TableCell>Cumulative Reward Ratio</TableCell>
</TableRow>
</TableHead>
<TableBody>
{delegations?.delegations.map((delegation: any) => (
<TableRow key={delegation.mix_id}>
<TableCell>{delegation.mix_id}</TableCell>
<TableCell>{delegation.owner}</TableCell>
<TableCell>{delegation.amount.amount}</TableCell>
<TableCell>{delegation.cumulative_reward_ratio}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
)}
</Box>
{delegations && (
<Box marginBottom={3}>
<Button variant="outlined" onClick={() => doUndelegateAll()} disabled={undeledationLoader}>
{undeledationLoader ? 'Undelegating...' : 'Undelegate All'}
</Button>
</Box>
)}
<Box>
<Button variant="outlined" onClick={() => doWithdrawRewards()} disabled={withdrawLoading}>
{withdrawLoading ? 'Doing withdraw...' : 'Withdraw rewards'}
</Button>
</Box>
</Box>
</Box>
</Paper>
<Box marginTop={3}>
<Typography variant="h5">Transaction Logs:</Typography>
{log}
</Box>
</Box>
);
};
+37 -8
View File
@@ -1,21 +1,50 @@
// const path = require('path');
// const CopyPlugin = require('copy-webpack-plugin');
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx',
});
console.dir(withNextra(), { depth: 30 });
console.dir(withNextra().rewrites, { depth: 30 });
const nextra = withNextra();
nextra.webpack = (config, options) => {
// generate Nextra's webpack config
const newConfig = withNextra().webpack(config, options);
newConfig.module.rules.push({
test: /\.txt$/i,
use: 'raw-loader',
});
// TODO: figure out how to properly bundle WASM and workers with Nextra
// newConfig.plugins.push(
// new CopyPlugin({
// patterns: [
// {
// from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*.wasm'),
// to: '[name][ext]',
// context: path.resolve(__dirname, 'out'),
// },
// {
// from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*worker*.js'),
// to: '[name][ext]',
// context: path.resolve(__dirname, 'out'),
// },
// ],
// }),
// );
return newConfig;
};
const config = {
...withNextra(),
output: 'export',
rewrites: undefined,
...nextra,
// output: 'export', // static HTML files, has problems with Vercel
// rewrites: undefined,
images: {
unoptimized: true,
},
basePath: process.env.NODE_ENV === 'development' ? undefined : '/docs/sdk/typescript', // this makes the SDK docs appear at https://nymtech.net/docs/sdk/typescript
transpilePackages: ['@nymproject/contract-clients'],
};
// config.images.unoptimized = true;
module.exports = config;
File diff suppressed because it is too large Load Diff
+43 -11
View File
@@ -1,29 +1,61 @@
{
"name": "nym-sdk-docs",
"name": "nextra-docs-template",
"version": "0.0.1",
"description": "Nextra docs template",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"docs:prod:build": "./scripts/build-prod-docs.sh"
"lint:fix": "next lint --fix"
},
"repository": {
"type": "git",
"url": "git+https://github.com/shuding/nextra-docs-template.git"
},
"author": "Shu Ding <g@shud.in>",
"license": "MIT",
"bugs": {
"url": "https://github.com/shuding/nextra-docs-template/issues"
},
"homepage": "https://github.com/shuding/nextra-docs-template#readme",
"dependencies": {
"@cosmjs/amino": "^0.31.0",
"@cosmjs/cosmwasm-stargate": "^0.31.0",
"@cosmos-kit/keplr": "^2.1.0",
"@cosmos-kit/react": "^2.1.1",
"@interchain-ui/react": "^1.3.3",
"chain-registry": "^1.18.0",
"next": "^13.0.6",
"@cosmjs/amino": "^0.31.1",
"@cosmjs/cosmwasm-launchpad": "^0.25.6",
"@cosmjs/cosmwasm-stargate": "^0.31.1",
"@cosmjs/encoding": "^0.31.1",
"@cosmjs/proto-signing": "^0.31.1",
"@cosmjs/stargate": "^0.31.0",
"@cosmos-kit/core": "^2.6.2",
"@cosmos-kit/keplr": "^2.3.9",
"@cosmos-kit/ledger": "^2.4.3",
"@cosmos-kit/react": "^2.8.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@interchain-ui/react": "^1.8.0",
"@mui/icons-material": "^5.14.9",
"@mui/lab": "^5.0.0-alpha.145",
"@mui/material": "^5.14.8",
"@nymproject/contract-clients": "^1.2.0-rc.5",
"@nymproject/mix-fetch": "^1.2.0-rc.5",
"@nymproject/mix-fetch-full-fat": "^1.2.0-rc.5",
"@nymproject/sdk-full-fat": "^1.2.0-rc.5",
"chain-registry": "^1.19.0",
"cosmjs-types": "^0.8.0",
"next": "^13.4.19",
"nextra": "latest",
"nextra-theme-docs": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"save": "^2.9.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/node": "18.11.10",
"copy-webpack-plugin": "^11.0.0",
"eslint": "8.46.0",
"eslint-config-next": "13.4.13",
"raw-loader": "^4.0.2",
"typescript": "^4.9.3"
}
}
+27
View File
@@ -0,0 +1,27 @@
import React, { useMemo } from 'react';
import type { AppProps } from 'next/app';
import './styles.css';
import { ThemeProvider, createTheme } from '@mui/material/styles';
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
const muiTheme = useMemo(
() =>
createTheme({
palette: {
mode: 'dark',
primary: {
main: '#e67300',
},
},
}),
[],
);
const AnyComponent = Component as any;
return (
<ThemeProvider theme={muiTheme}>
<AnyComponent {...pageProps} />
</ThemeProvider>
);
};
export default MyApp;
+13 -3
View File
@@ -1,6 +1,16 @@
{
"index": "Introduction",
"sdk": "Typescript SDK",
"examples": "Example usage and apps",
"blockchain": "Nym Cosmos chain"
"overview": "SDK overview",
"installation": "Installation",
"start": "Getting started",
"guides": "Examples",
"playground": "Live Playground",
"contact": {
"title": "Contact ↗",
"type": "page",
"href": "https://nymtech.net/",
"newWindow": true
}
}
@@ -1,3 +0,0 @@
{
"wallet": "Wallet support"
}
@@ -1,13 +0,0 @@
You can try out interacting with the Nyx chain that runs the Nym smart contracts by connecting your wallet:
```tsx
// TODO - move this out of fencing and fix up compile errors
import { Wallet } from '../components/wallet';
<Wallet/>
```
Nulla reprehenderit nostrud culpa consectetur culpa occaecat tempor culpa tempor eiusmod. Ullamco amet labore culpa ipsum ullamco veniam ex sunt et. Pariatur qui consectetur commodo irure non laboris officia in ex dolor eu. Quis est quis eiusmod non cillum ea aliqua amet excepteur deserunt. Esse commodo anim Lorem enim nostrud veniam ex cillum commodo proident laboris ad dolore. Lorem ea veniam ex cillum adipisicing ut sunt minim voluptate cupidatat labore voluptate eiusmod irure. Laborum ipsum ullamco fugiat elit ullamco officia laborum nulla aute ea et tempor labore id.
+55
View File
@@ -0,0 +1,55 @@
# Troubleshooting Bundling
You might need some help bundling packages from the Nym Typescript SDK into your package.
Here are some things that could go wrong:
## WebAssembly (WASM) and web worker not included in output bundle
### Webpack
You might need to use the CopyPlugin by adding this to your Webpack config:
```js
const CopyPlugin = require('copy-webpack-plugin');
...
module.exports = {
...
plugins: [
...
new CopyPlugin({
patterns: [
{
from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*.wasm'),
to: '[name][ext]',
},
{
from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*worker*.js'),
to: '[name][ext]',
},
],
}),
],
}
```
How does this work? The statement `require.resolve('@nymproject/mix-fetch/package.json')` finds the disk location of
the Nym SDK package, and resolve the directory name is `path.dirname`, the add the `*.wasm` glob to the search pattern
list. Use `[name][ext]` to preserve the output filename, because the package expects the filename to stay the same.
## ESM not supported
If your bundler does not support ECMAScript Modules (ESM) we provide CommonJS packages for most parts of the SDK.
For those that don't have ESM versions, you will need to use a tool like [Babel](https://babeljs.io/) to convert
ESM to CommonJS.
## CSP prevents loading
If you are using a `*-full-fat` package, or if you inline WASM or web workers, you may not be able to load them if the
[CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents WASM from being instantiated from a string.
You'll have to experiment with either adjusting the CSP or use another variant that is unbundled.
@@ -1,5 +0,0 @@
{
"index": "List of live examples",
"community": "Apps made by the Nym community",
"playground": "Interactive playground"
}
@@ -1,8 +0,0 @@
# Community Typescript and Javascript apps
Here is a list of apps built by our awesome community using the Nym Typescript SDK:
- project 1
- project 2
- project 3
@@ -1,12 +0,0 @@
# Examples
Here are some live examples of what you can do with the Typescript SDK:
- Chat app
- Node tester
- mixFetch demo
And some examples of usage:
- Firefox browser extension
- Chrome browser extension
@@ -1,7 +0,0 @@
# Try out the Typescript SDK live in your browser
Commodo magna ullamco voluptate aute adipisicing culpa duis reprehenderit. Laboris fugiat in duis mollit dolor voluptate ex anim. Cupidatat enim nostrud laboris incididunt minim do do et adipisicing. Adipisicing pariatur dolor elit nisi elit. Cillum nisi irure officia ipsum. Pariatur ex esse dolor sit esse consectetur dolore dolor. Nostrud dolor proident ullamco sint commodo laboris culpa.
Occaecat commodo deserunt ut laborum pariatur exercitation ea cillum officia non. Cupidatat amet aute ex exercitation magna quis fugiat consequat minim exercitation fugiat deserunt Lorem deserunt. Commodo irure incididunt officia et aute.
Ut voluptate deserunt sunt voluptate elit. Incididunt enim nulla amet occaecat incididunt officia sunt eu fugiat dolore id anim. Aliqua duis voluptate consectetur ex voluptate nisi sit quis esse nulla irure aliquip.
@@ -0,0 +1,6 @@
{
"nym-smart-contracts": "1. Nym Smart Contracts",
"mixnet": "2. Mixnet Client",
"mix-fetch": "3. mixFetch",
"cosmos-kit": "4. Cosmos Kit"
}
@@ -0,0 +1,41 @@
# Cosmos Kit
The wonderful people of Cosmology have made some [fantastic components](https://cosmoskit.com/) that can be used with
Nym, these include:
- using the wallets such as Keplr, Cosmostation and others from your React application
- using the [Ledger hardware wallet](https://docs.cosmoskit.com/integrating-wallets/ledger) from the browser
- any wallet that supports [Wallet Connect v2.0](https://docs.cosmoskit.com/integrating-wallets/adding-new-wallets)
```ts
import React from 'react';
import { useChain } from '@cosmos-kit/react';
import { assets, chains } from 'chain-registry';
import { wallets } from '@cosmos-kit/keplr';
export const MyComponent = () => {
const { wallet, address, connect, getOfflineSignerDirect } =
useChain('nyx');
React.useEffect(() => {
connect();
}, []);
const sign = async () => {
const doc = { ... };
return getOfflineSignerDirect().signDirect(address, doc);
};
return (
<div>
<div>
<strong>Connected to {wallet.prettyName}</strong>
</div>
<div>
Address: <code>{address}</code>
</div>
</div>
);
}
```
@@ -0,0 +1,44 @@
# `mixFetch`
An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`:
```
npm install @nymproject/mix-fetch
```
And then:
```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}` }
});
```
Sounds great, are there any catches? Well, there are a few (for now):
1. Currently, the operators of Network Requesters that make the final request at the egress part of the Nym Mixnet to
the internet use a [standard allow list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt)
in combination with their own configuration. If you are trying to access something that is not on the allow list, you
have two choices:
- run your own Network Requester and locally configure it to allow the hosts you need to connect to
- get in touch with us and give us more information about the sites you want included in the standard allow list
2. We periodically update the CA certificates in `mixFetch` so if you get a certificate error, we may not have the
root CA certificate you need in our list. [Send us a PR](https://github.com/nymtech/nym/pulls) if you need changes.
3. If you are using `mixFetch` in a web app with HTTPS you will need to use a gateway that has Secure Websockets to
avoid getting a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error.
+106
View File
@@ -0,0 +1,106 @@
import { Callout } from 'nextra/components'
# Mixnet Client
As you know by now, in order to send or receive messages over the mixnet, you'll need to use the [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk), which will allow you to create apps that can use the Nym Mixnet and Coconut credentials.
This client is message based - it can only send a one-way message to another client's address.
Replying can be done in two ways:
- reveal the sender's address to the recipient (as part of the payload)
- use a SURB (single use reply block) that allows the recipient to reply to the sender without compromising the identity of either party
##### Imports
Import the SDK's Mixnet Client as well as the payload in your app:
````js
import { createNymMixnetClient, NymMixnetClient, Payload } from "@nymproject/sdk-full-fat";
````
##### Example: using the SDK's Mixnet Client to send and receive messages over the Nym Mixnet
<Callout type="info" emoji="️">
For this example, we will be using the `full-fat` version of the ESM SDK. If you'd like to use the unbundled ESM one, make sure your [bundler configuration](../../bundling) copies the WebAssembly (WASM) and web worker files to the output bundle.
</Callout>
```ts
import { useEffect, useState } from "react";
import {
createNymMixnetClient,
NymMixnetClient,
Payload,
} from "@nymproject/sdk-full-fat";
const nymApiUrl = "https://validator.nymtech.net/api";
export const Traffic = () => {
const [nym, setNym] = useState<NymMixnetClient>();
const [selfAddress, setSelfAddress] = useState<string>();
const [recipient, setRecipient] = useState<string>();
const [payload, setPayload] = useState<Payload>();
const [receivedMessage, setReceivedMessage] = useState<string>();
const init = async () => {
const nym = await createNymMixnetClient();
setNym(nym);
// start the client and connect to a gateway
await nym?.client.start({
clientId: crypto.randomUUID(),
nymApiUrl,
});
// check when is connected and set the self address
nym?.events.subscribeToConnected((e) => {
const { address } = e.args;
setSelfAddress(address);
});
// show whether the client is ready or not
nym?.events.subscribeToLoaded((e) => {
console.log("Client ready: ", e.args);
});
// show message payload content when received
nym?.events.subscribeToTextMessageReceivedEvent((e) => {
console.log(e.args.payload);
setReceivedMessage(e.args.payload);
});
};
const send = () => nym.client.send({ payload, recipient });
useEffect(() => {
init();
}, []);
if (!nym) return <div>waiting for the mixnet client...</div>;
if (!selfAddress) return <div>connecting...</div>;
return (
<div>
<h1>Send messages through the Mixnet</h1>
<p style={{ border: "1px solid black" }}>
My self address is: {selfAddress ? selfAddress : "loading"}
</p>
<div style={{ border: "1px solid black" }}>
<label>Recipient Address</label>
<input
type="text"
onChange={(e) => setRecipient(e.target.value)}
></input>
<input
type="text"
onChange={(e) =>
setPayload({ message: e.target.value, mimeType: "text/plain" })
}
></input>
<button onClick={() => send()}>Send</button>
</div>
<p>Received message: {receivedMessage}</p>
</div>
);
};
```
@@ -0,0 +1,342 @@
import { Callout } from 'nextra/components'
# Nym Smart Contract Clients
As previously mentioned, to query or execute on any of the Nym contracts, you'll need to use one of the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all of Nym's smart contracts.
##### Contract Clients list
Lists of the diffent available clients and methods from the `Contract Clients` can be found in the `.client.ts` files:
| Client name | Functionality| Methods list |
| :-------------: | :----------: | :----------: |
| Coconut Bandwidth Client| Manages the depositing and release of funds. Tracks double spending. | [Coconut Bandwidth](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutBandwidth.client.ts) |
| Coconut DKG Client | Allows signers partcipating in issuing Coconut credentials to derive keys to be used. | [Coconut DKG](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutDkg.client.ts) |
| Cw3FlexMultisig Client | Used by the Coconut APIs to issue credentials. [This](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw3-flex-multisig) is a multisig contract that is backed by the cw4 (group) contract, which independently maintains the voter set. | [Cw3Flex Multisig](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw3FlexMultisig.client.ts) |
| Cw4Group Client | Used by the Coconut APIs to issue credentials. [Cw4 Group](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw4-group) stores a set of members along with an admin, and allows the admin to update the state. | [Cw4Group](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw4Group.client.ts) |
| Mixnet Client | Manages the network topology of the mixnet, tracking delegations and rewarding | [Mixnet](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Mixnet.client.ts) |
| Name Service Client | Operates as a directory of user-defined aliases, analogous to a Domain Name System (DNS). | [Name service](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/NameService.client.ts) |
| Service provider Directory Client| Allows users to register their service provider in a public directory. | [Service Provider](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/ServiceProviderDirectory.client.ts) |
| Vesting Client | Manages NYM token vesting functionality. | [Vesting](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Vesting.client.ts) |
Depending on your app or project's architecture, this could be any of the ESM or CJS versions of the `Contract Clients`.
##### Set-up your environment
Create your directory and set-up your app environment:
```
npx create-react-app my-app
```
##### Installation
Install the package and its dependencies from Cosmos Stargate:
```
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing
```
## Query clients
##### Imports
Import the contracts' client in your app:
````js
import { contracts } from '@nymproject/contract-clients';
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
````
##### Polyfills
You will need to install:
`npm install --save url fs assert crypto-browserify stream-http https-browserify os-browserify buffer stream-browserify process react-app-rewired`
and create a `config-overrides.js`file:
```js
const webpack = require('webpack');
module.exports = function override(config, env) {
config.resolve.fallback = {
url: require.resolve('url'),
fs: require.resolve('fs'),
assert: require.resolve('assert'),
crypto: require.resolve('crypto-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
os: require.resolve('os-browserify/browser'),
buffer: require.resolve('buffer'),
stream: require.resolve('stream-browserify'),
};
config.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer'],
}),
);
return config;
}
```
Update your `package.json` file:
```json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject" // don't change the eject
},
```
##### Example: using the Mixnet smart contract client to query
In this example, we will use the `MixnetQueryClient`from the `Contract Clients` to simply query the contract and return a list of Mixnodes.
```js
import "./App.css";
import { contracts } from "@nymproject/contract-clients";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { useEffect, useState } from "react";
export default function Mixnodes() {
const [mixnodes, setMixnodes] = useState(null);
async function fetchMixnodes(){
// Set-up the CosmWasm Client
const cosmWasmClient = await SigningCosmWasmClient.connect("wss://rpc.nymtech.net:443");
const client = new contracts.Mixnet.MixnetQueryClient(
cosmWasmClient,
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr" // the contract address (which is different on mainnet, QA, etc)
);
console.log("client:", client)
const result = await client.getMixNodesDetailed({});
console.log(result)
setMixnodes(result.nodes)
}
useEffect(() => {
fetchMixnodes();
}, [])
return(
<>
<table>
<tbody>
{mixnodes?.map((value, index) => {
return(
<tr key={index}>
<td> {value?.bond_information?.mix_node?.identity_key} </td>
</tr>
)
})
}
</tbody>
</table>
</>
)
}
```
## Execute clients
##### Imports
Import the contracts' execute clients in your app:
````js
import { contracts } from '@nymproject/contract-clients';
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
````
##### Example: using the Mixnet smart contract client to execute methods
In this example, we will use the `MixnetClient`and the `signer` from the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients) to execute methods.
Note that for the `settings.ts` file, we have used the following structure:
```json
export const mySettings = {
url: "wss://rpc.nymtech.net:443",
mixnetContractAddress: <ENTER MIXNET CONTACT ADDRESS HERE>,
mnemonic: '<ENTER MNEMONIC HERE>,
address: <ENTER NYM ADDRESS HERE>
};
export const settings = mySettings;
```
```js
import "./App.css";
import { contracts } from "@nymproject/contract-clients";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { GasPrice } from "@cosmjs/stargate";
import { settings } from "./settings.ts";
export default function Exec() {
let signer = null;
let address = null;
let signerMixnetClient = null;
let cosmWasmSigningClient = null;
let mixId = null;
let amountToDelegate = null;
let balance = null;
let nodeAddress = null;
let amountToSend = null;
let delegations = null;
async function ExecuteOnNyx() {
// Signer
try {
// Generate a signer from a mnemonic
signer = await DirectSecp256k1HdWallet.fromMnemonic(settings.mnemonic, {
prefix: "n",
});
const accounts = await signer.getAccounts();
address = accounts[0].address;
} catch (error) {
console.error("Problem getting the signer: ", error);
}
try {
const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner(
settings.url,
signer,
{
gasPrice: GasPrice.fromString("0.025unym"),
}
);
cosmWasmSigningClient = cosmWasmClient;
try {
balance = await cosmWasmSigningClient?.getBalance(address, "unym");
console.log("balance", balance);
} catch (error) {
console.error("problem geting the balance: ", error);
}
const mixnetClient = new contracts.Mixnet.MixnetClient(
cosmWasmSigningClient,
settings.address, // sender (that account of the signer)
settings.mixnetContractAddress // contract address (different on mainnet, QA, etc)
);
signerMixnetClient = mixnetClient;
} catch (error) {
console.error("Problem getting the cosmWasmSigningClient: ", error);
}
}
// Get delegations
const getDelegations = async () => {
if (!signerMixnetClient) {
return;
}
const delegationsObject = await signerMixnetClient.getDelegatorDelegations({
delegator: settings.address,
});
delegations = delegationsObject;
};
// Make delegation
const doDelegation = async () => {
if (!signerMixnetClient) {
return;
}
try {
const res = await signerMixnetClient.delegateToMixnode(
{ mixId },
"auto",
undefined,
[{ amount: `${amountToDelegate}`, denom: "unym" }]
);
console.log("delegations: ", res);
} catch (error) {
console.error(error);
}
};
// Undelegate all
const doUndelegateAll = async () => {
if (!signerMixnetClient) {
return;
}
console.log("delegations", delegations);
try {
for (const delegation of delegations.delegations) {
await signerMixnetClient.undelegateFromMixnode(
{ mixId: delegation.mix_id },
"auto"
);
}
} catch (error) {
console.error(error);
}
};
// Sending tokens
const doSendTokens = async () => {
const memo = "test sending tokens";
try {
const res = await cosmWasmSigningClient.sendTokens(
settings.address,
nodeAddress,
[{ amount: amountToSend, denom: "unym" }],
"auto",
memo
);
console.log("res", res);
} catch (error) {
console.error(error);
}
};
ExecuteOnNyx();
setTimeout(() => getDelegations(), 1000);
return (
<div>
<p>Exec</p>
<div>
<p>Send Tokens</p>
<input
type="string"
placeholder="Node Address"
onChange={(e) => (nodeAddress = e.target.value)}
/>
<input
type="number"
placeholder="Amount"
onChange={(e) => (amountToSend = e.target.value)}
/>
<div>
<button onClick={() => doSendTokens()}>Send Tokens</button>
</div>
</div>
<div>
<p>Delegate</p>
<input
type="number"
placeholder="Mixnode Id"
onChange={(e) => (mixId = e.target.value)}
/>
<input
type="number"
placeholder="Amount"
onChange={(e) => (amountToDelegate = e.target.value)}
/>
<div>
<button onClick={() => doDelegation()}>Delegate</button>
</div>
<div>
<button onClick={() => doUndelegateAll()}>Undelegate All</button>
</div>
</div>
</div>
);
}
```
+34 -1
View File
@@ -1,3 +1,36 @@
# Introduction
Welcome to the documentation for the Nym Typescript SDK.
Welcome to the documentation for Nym's TypeScript SDK! <br/>
This guide contains valuable information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack, including the mixnet, Nyx chain, and Coconut credentials.
### Our other developer guides
If you're new to the Nym ecosystem and aiming to understand the mixnet concept, explore kickstart options and demos, learn network integration, or follow developer tutorials, the [Developer Portal](https://nymtech.net/developers/) is your go-to resource.
For a more in-depth exploration of Nym's architecture, clients, nodes, and SDK examples, we recommend referring to the [Technical Documentation](https://nymtech.net/docs/) section.
If you're looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways, network requesters) and Nyx blockchain validators, then have a look at our [Operators Guide](https://nymtech.net/operators/introduction.html).
### What is Nym?
Nym is a **privacy infrastructure that secures user data and protects against surveillance at the network level**.
The platform does so by leveraging different technological components:
- **A Mixnet**, a type of overlay network that makes both content and metadata of transactions private through network-level obfuscation and incentivisation (using [Sphinx](https://blog.nymtech.net/sphinx-tl-dr-the-data-packet-that-can-anonymize-bitcoin-and-the-internet-18d152c6e4dc));
- **A blockchain, [Nyx](https://blog.nymtech.net/nym-is-not-a-blockchain-but-it-is-powered-by-one-4bb16ef16587)**, our Cosmos SDK blockchain, to allow for us to use payment tokens in the form of NYM, as well as smart contracts, in order to create a robust, decentralized, and secure environment incentives for the mixnet;
- **Coconut, a signature scheme** that creates an application-level private access control layer to power [Zk-Nyms](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a);
- **A utility token, [NYM](https://blog.nymtech.net/nym-tokens-where-do-they-live-and-how-are-they-distributed-cross-chain-8d134bf9c41f)**, to pay for usage, measure reputation and serve as rewards for the privacy infrastructure.
Simply put, the Nym network ("Nym") is a decentralized and incentivized infrastructure to provision privacy to a broad range of message-based applications and services.
### Read our protocol
[The Nym network ("Nym")](https://www.feat-nym-update-nym-web.websites.dev.nymte.ch/nym-whitepaper.pdf) is a privacy infrastructure that, simply put, can be seen as a ["Layer 0" privacy infrastructure](https://blog.nymtech.net/nym-layer-0-privacy-infrastructure-for-the-whole-internet-e53238f9b8e7) for the entire internet.
Read more to understand the differences in between Nym, TOR (and other mixnets) and VPNs.
@@ -0,0 +1,65 @@
import { Callout } from 'nextra/components'
# Overview
The Typescript SDK's different modules allow developers to start building browser-based applications quickly, by simply importing the SDK module of their choice - depending on the component from the Nym architecture they want to use - into their code via NPM as they would any other Typescript library.
<Callout type="info" emoji="️">
SDK modules come in four different flavours (ESM, CJS and full-fat for ESM and CJS).
This documentation only shows instructions and examples for the unbundled ESM variant.
</Callout>
#### Install all
```
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk @nymproject/mix-fetch --save
```
## Nym Smart Contracts
#### Overview
The Nyx blockchain is a general-purpose CosmWasm-enabled smart contract platform, and the home of the smart contracts which keep track of the mixnet, amongst others.
Information about the chain can be found on the [Nyx blockchain explorer](https://nym.explorers.guru/).
Using the [Nym Mixnet smart contract clients](https://nymtech.net/docs/nyx/smart-contracts.html), you will be able to query contract states or execute methods when providing a signing key.
*You can learn about our different methods to interact with the chain [here](https://nymtech.net/docs/nyx/interacting-with-chain.html)*.
#### Installation: Contract Clients
In order to query or execute on any of the Nym smart contracts, you'll need to use the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all Nyx's smart contracts.
First install the package and its dependencies from Cosmos Stargate:
```
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing --save
```
## Mixnet
#### Overview
The [Nym mixnet](https://nymtech.net/docs/architecture/network-overview.html) provides very strong security guarantees against network-level surveillance. It wraps into packets and mixes together IP traffic from many users inside the mixnet. It encrypts and mixes [Sphinx packet](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the Loopix design.
*You can explore our mixnet using our [mixnet explorer](https://nymtech.net/docs/explorers/mixnet-explorer.html) here.*
#### Installation: Mixnet Client
In order to send or receive traffic over the mixnet, you'll need to use the [`Mixnet Client`](https://www.npmjs.com/package/@nymproject/sdk).
First install the package and its dependencies:
```
npm install @nymproject/sdk --save
```
## MixFetch
#### Overview
MixFetch is a drop-in replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) that sends HTTP requests through the Mixnet. It does this by grabbing the same arguments as traditional fetch and constructing a SOCKS5 request that will be made to the destination host on the Internet via a [SOCKS5](https://nymtech.net/developers/quickstart/socks-proxy.html) [Network Requester](https://nymtech.net/docs/nodes/network-requester.html).
#### Installation: MixFetch package
In order to fetch data through mixFetch you'll need to use the [`MixFetch package`](https://www.npmjs.com/package/@nymproject/mix-fetch).
First install the package and its dependencies:
```
npm install @nymproject/mix-fetch --save
```
+72
View File
@@ -0,0 +1,72 @@
import { Callout } from 'nextra/components'
import { TableContainer, Table, TableBody, TableCell, TableRow, Paper } from '@mui/material'
import { NPMLink } from '../components/npm';
## SDK overview
The Typescript SDK allows developers to start building browser-based mixnet applications quickly, by simply importing the SDK modules into their code via NPM as they would any other Typescript library.
Currently developers can use different packages from the Typescript SDK to do the following entirely in the browser:
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell>
**Nym Smart Contracts**
</TableCell>
<TableCell>
Create a simple query or query and execute methods on the smart contracts that run the Nym Mixnet
</TableCell>
<TableCell>
<NPMLink packageName={'@nymproject/contract-clients'} kind={'esm'}/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
**Mixnet Client**
</TableCell>
<TableCell>
Send & receive text and binary traffic over the Nym Mixnet
</TableCell>
<TableCell>
<NPMLink packageName={'@nymproject/sdk'} kind={'esm'}/><br/>
<NPMLink packageName={'@nymproject/sdk-full-fat'} kind={'esm'} preBundled/><br/>
<NPMLink packageName={'@nymproject/sdk-commonjs'} kind={'cjs'}/><br/>
<NPMLink packageName={'@nymproject/sdk-full-fat-commonjs'} kind={'cjs'} preBundled/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
**mixFetch**
</TableCell>
<TableCell>
A drop-in replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
that sends HTTP requests over the Nym Mixnet
</TableCell>
<TableCell>
<NPMLink packageName={'@nymproject/mix-fetch'} kind={'esm'}/><br/>
<NPMLink packageName={'@nymproject/mix-fetch-full-fat'} kind={'esm'} preBundled/><br/>
<NPMLink packageName={'@nymproject/mix-fetch-commonjs'} kind={'cjs'}/><br/>
<NPMLink packageName={'@nymproject/mix-fetch-full-fat-commonjs'} kind={'cjs'} preBundled/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
## Which package should I use?
All packages come in four different variations:
- **ESM**: For new projects with current tooling. These packages use the ECMAScript Modules (ESM) system. You may need to [configure your bundler](bundling) to handle the packages WASM and Web Worker components;
- **ESM full-fat**: These ESM packages are pre-bundled and include inline WebAssembly and web worker code;
- **CommonJS**: For older projects that still use CommonJS. All WebAssembly (WASM) and Web Workers in the package need to be [bundled](bundling) to work correctly;
- **CommonJS full-fat**: These packages are already pre-bundled and should work in your project as is;
<Callout type="warning" emoji="🥛">
All `*-full-fat` variants have large bundle sizes because they include all WASM and web-workers as inline Base64 strings. If you care about your app's bundle size, then use the ESM variant.
</Callout>
<Callout type="info" emoji="⚠️">
As the Typescript SDK and associated modules are still a work in progress, note that the inline WASM and web worker versions are likely to have issues if the web site / app / extension has a strict [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).
</Callout>
@@ -0,0 +1,7 @@
{
"mixnodes": "1. Query mixnet contract for a list of mix nodes",
"wallet": "1.2. Basic wallet operations",
"traffic": "2. Send traffic over the mixnet",
"mixfetch": "3. Use mixFetch",
"cosmos-kit": "4. Cosmos Kit"
}
@@ -0,0 +1,28 @@
import "@interchain-ui/react/styles"
import { CosmosKit } from "../../components/cosmos-kit";
import { Callout } from 'nextra/components'
import FormattedCosmoskitExampleCode from '../../code-examples/cosmoskit-example-code.mdx';
# Cosmos Kit
Below is an example that uses [CosmosKit](https://cosmoskit.com/) to connect your [Keplr wallet](https://www.keplr.app/) or
[Ledger hardware wallet](https://www.ledger.com/) to this page:
<CosmosKit />
Once you connect either Keplr or your hardware Ledger, you can request a fake transaction to be signed. The hash
of the message will be displayed.
<Callout type="info" emoji="️">
No transactions will be broadcast. You will only be signing a transaction.
</Callout>
If you are using the Ledger hardware wallet, please make sure:
- you have the `cosmoshub` app installed on the Ledger
- it is connected to your computer
- it is unlocked
- the Cosmos Hub app is open
- grant permissions to your browser when you click the button above to connect to the Ledger (if you do not see a prompt, try another browser)
<FormattedCosmoskitExampleCode />
@@ -0,0 +1,11 @@
# mixFetch
import { MixFetch } from '../../components/mixfetch'
import Box from '@mui/material/Box';
import FormattedMixFetchExampleCode from '../../code-examples/mixfetch-example-code.mdx';
Look in the [standard allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) and
use the playground below to do a HTTP GET request to any host in the allowed list:
<MixFetch />
<FormattedMixFetchExampleCode />
@@ -0,0 +1,13 @@
# Query for Mixnodes
import { Mixnodes } from '../../components/mixnodes';
import Box from '@mui/material/Box';
import FormattedExampleCode from '../../code-examples/mixnodes-example-code.mdx';
The Nym Mixnet contract keeps a directory of all mixnodes that can be used to mix traffic.
Here is a live example of querying the Mixnet Contract for a paged list of mixnodes:
<Mixnodes />
<FormattedExampleCode />
@@ -0,0 +1,10 @@
# Traffic
import { Traffic } from '../../components/traffic';
import Box from '@mui/material/Box';
import FormattedTrafficExampleCode from '../../code-examples/traffic-example-code.mdx';
Use this tool to experiment with the Mixnet: send and receive messages!
<Traffic />
<FormattedTrafficExampleCode />
@@ -0,0 +1,11 @@
# Wallet
import { Wallet } from '../../components/wallet'
import Box from '@mui/material/Box';
import FormattedWalletExampleCode from '../../code-examples/wallet-example-code.mdx';
Here's a small wallet example for you to test some of the methods of our clients.
You can use the testnet address provided as recipient address, and to delegate just go to the Mixnodes list and paste in the Mix ID number of the Mixnode you'd like to delegate to.
<Wallet />
<FormattedWalletExampleCode />
-5
View File
@@ -1,5 +0,0 @@
{
"index": "Introduction",
"mix-fetch": "mixFetch",
"react": "React"
}
-9
View File
@@ -1,9 +0,0 @@
# Nym Typescript SDK
Commodo magna ullamco voluptate aute adipisicing culpa duis reprehenderit. Laboris fugiat in duis mollit dolor voluptate ex anim. Cupidatat enim nostrud laboris incididunt minim do do et adipisicing. Adipisicing pariatur dolor elit nisi elit. Cillum nisi irure officia ipsum. Pariatur ex esse dolor sit esse consectetur dolore dolor. Nostrud dolor proident ullamco sint commodo laboris culpa.
Occaecat commodo deserunt ut laborum pariatur exercitation ea cillum officia non. Cupidatat amet aute ex exercitation magna quis fugiat consequat minim exercitation fugiat deserunt Lorem deserunt. Commodo irure incididunt officia et aute.
Ut voluptate deserunt sunt voluptate elit. Incididunt enim nulla amet occaecat incididunt officia sunt eu fugiat dolore id anim. Aliqua duis voluptate consectetur ex voluptate nisi sit quis esse nulla irure aliquip.
Culpa proident aliqua eu in. Id Lorem eu nostrud cupidatat sint adipisicing ex esse tempor commodo. Enim labore commodo aute quis aliqua enim. Veniam minim sunt commodo cillum quis officia ea tempor adipisicing pariatur. Nisi ea in reprehenderit in nostrud. Mollit non proident cupidatat aliqua ut ea quis id exercitation labore excepteur nulla ex.
@@ -1,8 +0,0 @@
---
description: Send your HTTP requests over the Nym mixnet securely with a drop-in replacement for fetch
---
# mixFetch
Anim occaecat exercitation officia aliquip sit culpa incididunt labore sit aute sint incididunt consequat. Ex enim ullamco dolore laboris incididunt aliqua duis consectetur non voluptate. Dolore ut sunt ad commodo. Veniam enim culpa tempor consectetur excepteur pariatur adipisicing. Commodo adipisicing magna irure deserunt cupidatat excepteur cupidatat consectetur fugiat irure aliqua laboris qui. Officia incididunt adipisicing elit eu fugiat cupidatat non irure id dolor.
@@ -1,3 +0,0 @@
# Node Tester
Deserunt commodo tempor magna culpa qui consectetur reprehenderit. Pariatur ipsum nostrud mollit ex elit est reprehenderit enim deserunt. Et voluptate veniam proident sunt ea in. Aute aliquip do commodo aliqua duis Lorem. Ut sunt officia exercitation magna nisi ea commodo do nostrud ullamco cupidatat tempor est. Magna minim magna minim occaecat. Tempor nisi occaecat amet duis duis qui deserunt culpa ex.
@@ -1,3 +0,0 @@
{
"index": "Introduction"
}
@@ -1,9 +0,0 @@
# Nym React Components
Voluptate laborum sit deserunt mollit labore ex labore sunt. Aliquip culpa est veniam culpa dolore irure do veniam aliquip ullamco reprehenderit pariatur. Elit proident sit ullamco dolore deserunt aute minim. Irure consectetur minim elit et incididunt non fugiat ad deserunt consectetur ad. Veniam enim non ipsum duis anim duis duis cillum consectetur anim eiusmod sint veniam. Amet commodo dolor esse amet dolor quis labore elit. Tempor commodo ipsum enim consectetur sit culpa consequat nisi.
UI to show:
- address display
- address form text input (with validation)
- currency display
- currency form text input (with validation)
@@ -1,3 +0,0 @@
# React Hooks for the SDK
Voluptate laborum sit deserunt mollit labore ex labore sunt. Aliquip culpa est veniam culpa dolore irure do veniam aliquip ullamco reprehenderit pariatur. Elit proident sit ullamco dolore deserunt aute minim. Irure consectetur minim elit et incididunt non fugiat ad deserunt consectetur ad. Veniam enim non ipsum duis anim duis duis cillum consectetur anim eiusmod sint veniam. Amet commodo dolor esse amet dolor quis labore elit. Tempor commodo ipsum enim consectetur sit culpa consequat nisi.
@@ -1,9 +0,0 @@
# React support
Nym provides several React libraries:
We also suggest you look at [CosmosKit](https://docs.cosmoskit.com/) if you want to:
- use a wallet (Keplr, Cosmostation) in your app
- sign transactions with a Ledger hardware wallet
- query and execute methods on Nym's smart contracts

Some files were not shown because too many files have changed in this diff Show More