Compare commits

...

145 Commits

Author SHA1 Message Date
Jon Häggblad 6de2583f01 Move bunch of workflows to GH large runners 2023-10-17 23:06:13 +02:00
Jon Häggblad 1de86f7ad7 Merge pull request #4015 from nymtech/jon/ci-cleanup
ci: general cleanup
2023-10-17 23:02:41 +02:00
Jon Häggblad 833502ee35 Download wasm-opt release binaries instead of compiling 2023-10-17 22:58:25 +02:00
Jon Häggblad 9095da1e10 Install wasm-pacl in ci-sdk-docs-typescript 2023-10-17 22:57:24 +02:00
Jon Häggblad b5eb8e94f4 Rename a few workflows 2023-10-17 22:56:33 +02:00
Jon Häggblad 8377c17838 Remove unnecessary apt install 2023-10-17 22:55:46 +02:00
Jon Häggblad af018180d2 Switch to pull_request triggers on a few workflows 2023-10-17 22:55:00 +02:00
Jon Häggblad 5102fe9797 Remove some old build conditionals 2023-10-17 22:53:45 +02:00
Jon Häggblad 188e766106 Rename to ci-nym-api-tests 2023-10-17 22:52:15 +02:00
Jon Häggblad 5729123dd1 Rename to build-upload-binaries 2023-10-17 22:51:24 +02:00
Jon Häggblad 1935df960b Remove push triggers on a bunch of CI workflows 2023-10-17 22:50:30 +02:00
Jon Häggblad c39fd49b1f Remove commented out leftovers 2023-10-17 22:47:17 +02:00
Jon Häggblad bc6634fb6f ci: use custom-linux instead of custom-runner-linux (#4014) 2023-10-17 22:42:31 +02:00
Tommy Verrall 09941eb741 Merge pull request #4013 from nymtech/feature/ts-sdk-fixes 2023-10-17 19:09:49 +01:00
Lorexia 2fc0d51377 Update mixfetch documentation 2023-10-17 19:11:09 +02:00
Lorexia 1e1b69c3b5 Update mixfetch doc and ascii tree bug 2023-10-17 19:07:25 +02:00
Tommy Verrall 829296c0bb Merge pull request #4009 from nymtech/CI/CD-docs-patch
CI/CD-docs patch
2023-10-17 14:56:08 +01:00
Jon Häggblad 7a8c9317bc ci: create install-wasm-opt reusable action (#4012) 2023-10-17 15:35:44 +02:00
serinko 11ca9dd34e fix flow 2023-10-17 13:24:03 +00:00
serinko 3e48b8db92 build books locally - success 2023-10-17 15:19:52 +02:00
serinko 38377ca776 edit syntax logic 2023-10-17 13:03:21 +00:00
serinko 529ad0e146 edit command path syntax 2023-10-17 12:43:01 +00:00
Tommy Verrall e328898971 Merge pull request #4005 from nymtech/dependabot/npm_and_yarn/babel/traverse-7.23.2
build(deps): bump @babel/traverse from 7.22.17 to 7.23.2
2023-10-17 11:44:26 +01:00
serinko 72a6de18ae edit syntax 2023-10-17 10:38:03 +00:00
serinko 52dc25b0ea correct path 2023-10-17 10:34:16 +00:00
serinko a6180a54bf serinko/patch/ci-docs
I was not finished with the previous PR - was merged too fast
2023-10-17 10:27:35 +00:00
Tommy Verrall f9971fbc8d Merge pull request #4008 from nymtech/patch/ci-docs/serinko
Fix ci/cd-docs errors
2023-10-17 11:20:54 +01:00
serinko 0c7df5bd22 add mdbook-admonish install 2023-10-17 10:04:03 +00:00
Jon Häggblad 96a925c040 wireguard: create structs for udp handler and tun device (#4007)
* Extract out parse_peer

* wip: handle_packet extract

* Extract out active_peers.rs

* wip: rework to struct from free function

* udp_listener working

* wip

* more udp_listener

* tun_device

* wip

* tun_device

* Remove some old commented out stuff

* tidy

* Remove commented out line
2023-10-17 12:01:11 +02:00
serinko 20bc1e9caf fix ci-docs error atempt 2023-10-17 09:50:44 +00:00
dependabot[bot] 2ae61ae79f build(deps): bump @babel/traverse from 7.22.17 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.17 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 16:08:39 +00:00
Tommy Verrall 53ab49cdd0 Merge pull request #4004 from nymtech/dependabot/npm_and_yarn/nym-api/tests/babel/traverse-7.23.2
build(deps-dev): bump @babel/traverse from 7.18.11 to 7.23.2 in /nym-api/tests
2023-10-16 16:56:27 +01:00
Tommy Verrall e341a37dd3 Merge pull request #3980 from nymtech/feature/operators/legal-forum
[DOC] Community legal forum for node operators
2023-10-16 16:34:52 +01:00
Tommy Verrall e6ca58b7c8 Merge pull request #3982 from nymtech/bug/issue-3981/wallet-delegations-orderby-cp
Wallet: Fix delegation sorting for Cost Params
2023-10-16 16:26:03 +01:00
Tommy Verrall 8a3959b1e1 Update ci-docs.yml
fix issue when admonish needs updating
2023-10-16 17:11:59 +02:00
Tommy Verrall 4ebeada604 Merge pull request #3999 from nymtech/feature/ts-sdk-fixes
Latest version of the TS SDK documentation
2023-10-16 15:49:54 +01:00
fmtabbara 6a43b95e5e merge develop 2023-10-16 15:36:37 +01:00
serinko 7ebb39c401 add tor legal advice excert 2023-10-16 16:24:24 +02:00
dependabot[bot] 66aff5bf2d build(deps-dev): bump @babel/traverse in /nym-api/tests
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.18.11 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:14:39 +00:00
Tommy Verrall 15e029c380 Merge pull request #3994 from nymtech/feature/developers-docs-update
Add TS SDK info and links to developers documentation
2023-10-16 15:13:36 +01:00
Tommy Verrall a17df31eaa Merge pull request #3993 from nymtech/feature/docs-update
Update TS SDK info in docs
2023-10-16 15:12:46 +01:00
Lorexia fa60c83691 Update github actions to bumped mdbook 2023-10-16 16:06:14 +02:00
Drazen Urch e4dda5e541 Replace HashMap with DashMap (#3995) 2023-10-16 15:40:26 +02:00
Jon Häggblad c598082335 wireguard: short circuit known addr (#3998)
* Forward directly on known addr

* Extract out test dev creation

* Extract out RegisteredPeers

* Extract out registered_peers.rs

* Reset main rate limiter on a timer

* Some pedantic clippy

* minor tidy

* Add missing continue
2023-10-16 14:15:33 +02:00
serinko 2e0cc8fa5f spellcheck 2023-10-16 14:07:45 +02:00
Lorexia b3caa7e28d Update TS SDK info in docs 2023-10-16 14:07:26 +02:00
Fouad 9a30708bea Ensure bonding page refresh when changing accounts (#4002) 2023-10-16 12:51:15 +01:00
Tommy Verrall dd5d13ce91 Merge pull request #3976 from nymtech/documentation/updates
[DOCS] Update admonish version && Fix formatting
2023-10-16 12:09:52 +01:00
Jon Häggblad 30bfc24386 Merge pull request #4001 from nymtech/master
Merge master into develop
2023-10-16 12:42:24 +02:00
Mark Sinclair 3a86e9ecb7 Run GitHub Actions for Typescript and MacOS on large runners
Conflicts:
	.github/workflows/publish-sdk-npm.yml
2023-10-16 12:35:23 +02:00
serinko 80fb3066e8 add how to make new input part 2023-10-16 12:17:52 +02:00
Lorexia 977338d1ba Update integration docs 2023-10-16 11:09:55 +02:00
Lorexia d5d889727d Update callouts 2023-10-16 11:09:55 +02:00
Lorexia 93d4f91008 Add note for mixnet in production 2023-10-16 11:09:55 +02:00
Lorexia c053dc5903 Update decision trees 2023-10-16 11:09:55 +02:00
Lorexia da847b6ee8 Update integrations info 2023-10-16 11:09:55 +02:00
Lorexia 0fea2e09ae Update decision tree 2023-10-16 11:09:55 +02:00
Lorexia 751e3f739a Update packages versions and integration decision tree 2023-10-16 11:09:42 +02:00
Lorexia 01b3204a49 Update docs: reorder pages, examples, fetch checker 2023-10-16 11:06:45 +02:00
Lorexia f5808fab83 Update fetch checker 2023-10-16 11:06:45 +02:00
Lorexia 48e698d080 Add details to integration page 2023-10-16 11:06:45 +02:00
Lorexia 022687526a Add fetch checker utility to integrations page 2023-10-16 11:06:45 +02:00
Gala 3a624070ad fix build 2023-10-16 11:06:45 +02:00
Gala 2e5a7c3912 different log for each comp 2023-10-16 11:06:45 +02:00
Gala 78f0633669 info txt 2023-10-16 11:06:45 +02:00
Gala 8fa54c4ad5 wrapping all wallet comp with wallet provider 2023-10-16 11:06:45 +02:00
Gala 7e07724085 fixing methods 2023-10-16 11:06:44 +02:00
Gala 42f0337e7f conecting things 2023-10-16 11:06:44 +02:00
Gala 7cab3d58a8 point to take in count 2023-10-16 11:06:44 +02:00
Gala 495f2eb543 wip 2023-10-16 11:06:44 +02:00
Gala 0baaa2f847 wip 2023-10-16 11:06:44 +02:00
Gala 4adf922b3f adding a react context... imposible to avoid it at the end : ( 2023-10-16 11:06:44 +02:00
Lorexia f3dfbeb2b7 Update examples 2023-10-16 11:06:44 +02:00
Lorexia 6619aed3b7 Update mixfetch for webpack 2023-10-16 11:06:44 +02:00
Lorexia 422b9a3a86 Add esbuild callout 2023-10-16 11:06:44 +02:00
Lorexia 618d0bdd34 Add disconnect to cosmoskit example 2023-10-16 11:06:44 +02:00
Lorexia aec136edc8 Update CosmosKit tutorial 2023-10-16 11:06:44 +02:00
Lorexia 88fa090e23 Update notes 2023-10-16 11:06:44 +02:00
Lorexia 988fb174fc Add details to mixfetch 2023-10-16 11:06:44 +02:00
Lorexia 8961d19fb8 Correct typo 2023-10-16 11:06:44 +02:00
Lorexia 627ebf4614 Fix mixnet example code for starting client 2023-10-16 11:06:44 +02:00
Lorexia 03099493aa Correct client 2023-10-16 11:06:44 +02:00
Lorexia f8c3f784c8 Update general FAQ 2023-10-16 11:06:44 +02:00
Lorexia 42fe861fbc Add testnet info to wallet 2023-10-16 11:06:43 +02:00
Lorexia a0ca5fcf55 Add RUST info 2023-10-16 11:06:43 +02:00
Gala 938e5ba19c refactor 2023-10-16 11:06:43 +02:00
Gala 962d43ba3f now give to each section it's own code example 2023-10-16 11:06:43 +02:00
Gala cd5888636c divide a the wallet in operations 2023-10-16 11:06:43 +02:00
Lorexia 305a374917 Update integrations page 2023-10-16 11:06:43 +02:00
serinko 02a74aa448 remove test file 2023-10-16 11:06:43 +02:00
serinko f86cb859a7 add test file 2023-10-16 11:06:43 +02:00
Lorexia 4bf7096ee7 Add callout to mixFetch page 2023-10-16 11:06:43 +02:00
Lorexia 4b830ce38b Correct documentation 2023-10-16 11:06:43 +02:00
Lorexia 65125b5f1e Update documentation: introduction, overview and installation 2023-10-16 11:06:43 +02:00
Lorexia 281c94c6b2 correct FAQ 2023-10-16 11:06:43 +02:00
Lorexia 160ffbbcdd repair ASCII tree bug 2023-10-16 11:06:43 +02:00
Gala a6dc10ceec updating dependencies 2023-10-16 11:06:30 +02:00
Lorexia 81edbab511 rebase to latest develop 2023-10-16 11:04:03 +02:00
Lorexia 272909d250 Add FAQ page details and rework bundling structure 2023-10-16 10:59:33 +02:00
Lorexia 91f1552a88 Add bundling pages and details for ESbuild and Webpack 2023-10-16 10:59:33 +02:00
Lorexia 4aa7d08e65 push last updates 2023-10-16 10:59:33 +02:00
Lorexia 587f500a0c add polyfills details to bundling page 2023-10-16 10:59:33 +02:00
Lorexia 98898054c3 update mixfetch, cosmoskit examples 2023-10-16 10:59:33 +02:00
Lorexia 4753766d16 update query, execute, mixnet examples 2023-10-16 10:59:33 +02:00
Lorexia 502acd5b20 update mixfetch example 2023-10-16 10:59:33 +02:00
Lorexia d569bf6b09 update mixnet example 2023-10-16 10:59:33 +02:00
Lorexia bfdf9942f0 update query and execute examples 2023-10-16 10:59:33 +02:00
Lorexia 63d2ed2fec add type annotations to examples 2023-10-16 10:59:33 +02:00
Lorexia 9627fa0500 Add small fixes 2023-10-16 10:59:33 +02:00
Lorexia 7938c41fcf Fix chips css 2023-10-16 10:59:33 +02:00
Lorexia f3fa86deb4 Fix css issues 2023-10-16 10:59:33 +02:00
Lorexia 3cdfcfff2c Fix buttons styling 2023-10-16 10:59:32 +02:00
Lorexia 1412ca8fdd Detail Cosmoskit example and add more fixes 2023-10-16 10:59:32 +02:00
Lorexia 611a945a3b Fix examples code 2023-10-16 10:59:32 +02:00
Tommy Verrall 7d3e2f9870 Merge pull request #3997 from nymtech/master
merge release/2023.2-bounty into develop
2023-10-16 09:41:59 +01:00
Tommy Verrall f08521a705 Merge pull request #3985 from nymtech/bug/issue-3984/fix-mixnode-sorting
Fix sorting for mixnodes and gateways
2023-10-16 09:16:07 +01:00
Tommy Verrall 3be06f813e Merge branch 'develop' into master 2023-10-16 09:06:01 +01:00
Jon Häggblad 2c8187eb6c wireguard: parse the public key up front in the UDP handler (#3977)
* wireguard: try to have a flow where we parse the public key up front

* Fix bug with continue instead of return in loop

* fix clippy::enum-variant-names
2023-10-16 09:44:10 +02:00
Tommy Verrall 82c107f7ad Merge pull request #3996 from nymtech/jon/clippy
Fix (some) clippy in beta toolchain
2023-10-16 08:07:43 +01:00
Tommy Verrall 3f5ec9e7be Merge pull request #3983 from nymtech/release/2023.2-bounty
Release/2023.2 bounty
2023-10-16 08:02:08 +01:00
Jon Häggblad ea606857c2 clippy in ephemera (not all) 2023-10-15 23:05:34 +02:00
Jon Häggblad a12c733b01 clippy::needless-pass-by-ref-mut 2023-10-15 22:41:46 +02:00
Jon Häggblad f9063a298b clippy::unnecessary-mut-passed 2023-10-15 22:40:52 +02:00
Jon Häggblad edc100e67e clippy::needless-pass-by-ref-mut 2023-10-15 22:39:52 +02:00
Mark Sinclair e381e9e37f Fix pre and post CI package loading 2023-10-13 19:22:33 +01:00
Mark Sinclair 1abcad05c1 GitHub Actions install wasm-opt 2023-10-13 19:10:39 +01:00
Mark Sinclair ffcfa9435f Run GitHub Actions for Typescript and MacOS on large runners 2023-10-13 18:58:32 +01:00
Mark Sinclair 52d5eb444b Fix up Typescript CI and linting 2023-10-13 18:40:47 +01:00
Lorexia b5cc7b8e49 Add TS SDK info and links to developers documentation 2023-10-13 18:35:29 +02:00
serinko 180927bcac add legal chat room 2023-10-13 16:39:05 +02:00
Tommy Verrall e02eae8fb3 Merge pull request #3986 from nymtech/bugfix/packet-type-satsub
use saturating sub in case outfox is not enabled
2023-10-13 10:03:02 +01:00
Jędrzej Stuczyński fe4870199e use saturating sub in case outfox is not enabled 2023-10-12 17:44:50 +01:00
fmtabbara 3d506cfa01 use switch statement for key mapping 2023-10-12 16:12:03 +01:00
fmtabbara 393d348306 fix sorting for mixnodes and gateways 2023-10-12 14:29:31 +01:00
fmtabbara 3ce936edac move delgation sorting logic to hook + update storybook data values for testing 2023-10-12 13:38:44 +01:00
Jon Häggblad 3b634fe64e wireguard: make sure to set rate limiter and index explicitly (#3978) 2023-10-12 14:26:11 +02:00
serinko 12a058d91b update mdbook-admonish 2023-10-12 13:46:30 +02:00
serinko 95f0dd8979 add in-sheet-break-lines <br> 2023-10-12 10:53:18 +00:00
serinko 02dfd775a9 add summary of smoosh legal impact 2023-10-12 10:28:27 +02:00
serinko 82d6d203f0 initialize legal forum 2023-10-12 10:15:02 +02:00
Mark Sinclair 70f5d476f2 Tidy up package.json workspace to prefer packages from npm 2023-10-11 14:58:44 +01:00
Mark Sinclair 445f3b0adb Update lock file 2023-10-11 14:58:14 +01:00
Mark Sinclair 42836b3e0e Add packages to version bumper internal tool 2023-10-11 14:57:57 +01:00
Mark Sinclair 7c2318a096 Release Typescript SDK v1.2.0 packages 2023-10-11 11:31:41 +01:00
benedettadavico 1b4bf74107 bump wallet version and update changelog 2023-10-10 09:47:46 +02:00
144 changed files with 4610 additions and 4509 deletions
@@ -0,0 +1,37 @@
name: 'Install wasm-opt'
description: 'Installs wasm-opt from binaryen'
inputs:
version:
description: 'Version of wasm-opt to install'
default: '116'
runs:
using: 'composite'
steps:
- name: Check platform compatibility
run: |
if [[ "$(uname)" != "Linux" ]]; then
echo "Error: This action is only compatible with Linux."
exit 1
fi
shell: bash
- name: Download wasm-opt
run: |
set -e
SOURCE="https://github.com/WebAssembly/binaryen/releases/download/version_${{ inputs.version }}/binaryen-version_${{ inputs.version }}-x86_64-linux.tar.gz"
TEMP_ARCHIVE="$RUNNER_TEMP/binaryen-version_${{ inputs.version }}-x86_64-linux.tar.gz"
curl -L -o "$TEMP_ARCHIVE" "$SOURCE"
tar -xvzf $TEMP_ARCHIVE -C $RUNNER_TEMP
echo "$RUNNER_TEMP/binaryen-version_${{ inputs.version }}/bin" >> $GITHUB_PATH
shell: bash
id: install-binary
- name: Verify installation
run: |
if ! command -v wasm-opt &> /dev/null; then
echo "Error: wasm-opt binary was not installed successfully."
exit 1
fi
shell: bash
id: verify-installation
@@ -1,16 +1,16 @@
name: Build and upload binaries to artifact storage
name: build-upload-binaries
on:
workflow_dispatch:
inputs:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
type: boolean
env:
NETWORK: mainnet
NETWORK: mainnet
jobs:
publish-nym:
+1 -1
View File
@@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v3
- name: Install rsync
+6 -6
View File
@@ -31,8 +31,8 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [custom-runner-linux]
platform: [custom-linux]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
@@ -45,12 +45,12 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Branch name
run: echo running on branch ${GITHUB_REF##*/}
- name: Run tests against binaries
run: ./build_and_run.sh ${{ github.head_ref || github.ref_name }}
working-directory: tests/
+5 -4
View File
@@ -1,13 +1,14 @@
name: ci-build-ts
on:
push:
pull_request:
paths:
- 'ts-packages/**'
- "ts-packages/**"
- "sdk/typescript/**"
jobs:
build:
runs-on: custom-runner-linux
runs-on: ubuntu-20.04-16-core
steps:
- uses: actions/checkout@v2
- name: Install rsync
@@ -20,7 +21,7 @@ jobs:
- name: Setup yarn
run: npm install -g yarn
- name: Build
run: yarn && yarn build && yarn build:ci
run: yarn && yarn build && yarn build:ci:storybook
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
+2 -22
View File
@@ -2,20 +2,6 @@ name: ci-build-upload-binaries
on:
workflow_dispatch:
push:
paths:
- 'clients/**'
- 'common/**'
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
pull_request:
paths:
- 'clients/**'
@@ -31,21 +17,16 @@ on:
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
platform: [ubuntu-20.04-16-core]
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
# a push event from the origin repo, or a PR from external repo
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'nymtech/nym' }}
steps:
- uses: actions/checkout@v3
@@ -59,8 +40,7 @@ jobs:
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
run: sudo apt update && sudo apt install libudev-dev
- name: Install Rust stable
uses: actions-rs/toolchain@v1
-3
View File
@@ -48,9 +48,6 @@ jobs:
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
# Enable sccache via environment variable
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
+1 -5
View File
@@ -2,10 +2,6 @@ name: ci-contracts-schema
on:
workflow_dispatch:
push:
paths:
- 'contracts/**'
- 'common/**'
pull_request:
paths:
- 'contracts/**'
@@ -14,7 +10,7 @@ on:
jobs:
check-schema:
name: Generate and check schema
runs-on: custom-runner-linux
runs-on: ubuntu-20.04
env:
CARGO_TERM_COLOR: always
steps:
@@ -2,10 +2,6 @@ name: ci-contracts-upload-binaries
on:
workflow_dispatch:
push:
paths:
- 'common/**'
- 'contracts/**'
pull_request:
paths:
- 'common/**'
@@ -16,16 +12,9 @@ env:
jobs:
publish-nym-contracts:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
runs-on: ubuntu-20.04-16-core
env:
CARGO_TERM_COLOR: always
# a push event from the origin repo, or a PR from external repo
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'nymtech/nym' }}
steps:
- uses: actions/checkout@v3
@@ -38,10 +27,6 @@ jobs:
mkdir -p $OUTPUT_DIR
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
@@ -50,7 +35,9 @@ jobs:
override: true
- name: Install wasm-opt
run: cargo install --version 0.112.0 wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '112'
- name: Build release contracts
run: make contracts
+10 -5
View File
@@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: custom-runner-linux
runs-on: ubuntu-20.04-16-core
steps:
- uses: actions/checkout@v3
- name: Install rsync
@@ -28,15 +28,20 @@ jobs:
command: build
args: --workspace --release --all
- name: Install mdbook
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook)
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.35" mdbook)
- name: Install mdbook plugins
run: |
cargo install --vers "=0.2.2" mdbook-variables && cargo install \
--vers "^1.8.0" mdbook-admonish && cargo install --vers \
--vers "^1.8.0" mdbook-admonish --force && cargo install --vers \
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \
&& cargo install --vers "^0.7.7" mdbook-linkcheck
&& cargo install --vers "^0.7.7" mdbook-linkcheck \
# && cd documentation \
# && mdbook-admonish install dev-portal \
# && mdbook-admonish install docs \
# && mdbook-admonish install operators
- name: Build all projects in documentation/ & move to ~/dist/docs/
run: cd documentation && ./build_all_to_dist.sh
run: cd documentation && ./build_all_to_dist.sh
continue-on-error: false
- name: Deploy branch to CI www
continue-on-error: true
+8 -11
View File
@@ -1,15 +1,6 @@
name: ci-lint-typescript
on:
push:
paths:
- "ts-packages/**"
- "sdk/typescript/**"
- "nym-connect/desktop/src/**"
- "nym-connect/desktop/package.json"
- "nym-wallet/src/**"
- "nym-wallet/package.json"
- "explorer/**"
pull_request:
paths:
- "ts-packages/**"
@@ -22,7 +13,7 @@ on:
jobs:
build:
runs-on: custom-runner-linux
runs-on: ubuntu-20.04-16-core
steps:
- uses: actions/checkout@v2
- uses: rlespinasse/github-slug-action@v3.x
@@ -37,9 +28,15 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '116'
- name: Set up Go
uses: actions/setup-go@v4
with:
@@ -49,7 +46,7 @@ jobs:
run: yarn
- name: Build packages
run: yarn build:ci:sdk
run: yarn build:ci
- name: Lint
run: yarn lint
@@ -1,16 +1,6 @@
name: ci-nym-connect-desktop-rust
on:
push:
paths:
- "nym-connect/desktop/src-tauri/**"
- "nym-connect/desktop/src-tauri/Cargo.toml"
- "clients/client-core/**"
- "clients/socks5/**"
- "common/**"
- "gateway/gateway-requests/**"
- "contracts/vesting/**"
- "nym-api/nym-api-requests/**"
pull_request:
paths:
- "nym-connect/desktop/src-tauri/**"
@@ -24,11 +14,9 @@ on:
jobs:
build:
runs-on: [self-hosted, custom-linux]
runs-on: ubuntu-20.04-16-core
env:
CARGO_TERM_COLOR: always
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev
+2 -2
View File
@@ -1,7 +1,7 @@
name: ci-nym-connect-desktop
on:
push:
pull_request:
paths:
- 'nym-connect/desktop/**'
@@ -11,7 +11,7 @@ defaults:
jobs:
build:
runs-on: custom-runner-linux
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Install rsync
@@ -12,7 +12,7 @@ defaults:
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
+1 -3
View File
@@ -16,11 +16,9 @@ on:
jobs:
build:
runs-on: [ self-hosted, custom-linux ]
runs-on: custom-linux
env:
CARGO_TERM_COLOR: always
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
+15 -4
View File
@@ -1,35 +1,44 @@
name: Nym Wallet Storybook
name: ci-nym-wallet-storybook
on:
push:
pull_request:
paths:
- 'nym-wallet/**'
jobs:
build:
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
run: sudo apt-get install rsync
continue-on-error: true
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Setup yarn
run: npm install -g yarn
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Build dependencies
run: yarn && yarn build
- name: Build storybook
run: yarn storybook:build
working-directory: ./nym-wallet
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
@@ -41,9 +50,11 @@ jobs:
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-wallet
+9 -5
View File
@@ -1,10 +1,6 @@
name: ci-sdk-docs-typescript
on:
push:
paths:
- "sdk/typescript/**"
- "wasm/**"
pull_request:
paths:
- "sdk/typescript/**"
@@ -12,7 +8,7 @@ on:
jobs:
build:
runs-on: custom-runner-linux
runs-on: ubuntu-20.04-16-core
steps:
- uses: actions/checkout@v2
- name: Install rsync
@@ -34,6 +30,14 @@ jobs:
with:
go-version: '1.20'
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '116'
- name: Build branch WASM packages
run: make sdk-wasm-build
+5 -4
View File
@@ -9,7 +9,7 @@ on:
jobs:
wasm:
runs-on: [custom-runner-linux]
runs-on: custom-linux
env:
CARGO_TERM_COLOR: always
steps:
@@ -18,7 +18,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: actions-rs/toolchain@v1
with:
profile: minimal
@@ -32,12 +32,13 @@ jobs:
with:
go-version: '1.20'
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '116'
- name: Install wasm-bindgen-cli
run: cargo install wasm-bindgen-cli
+1 -1
View File
@@ -1,4 +1,4 @@
name: Greetings
name: greetings
on: [pull_request_target, issues]
+1 -1
View File
@@ -70,7 +70,7 @@ jobs:
notification:
needs: build
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
@@ -68,7 +68,7 @@ jobs:
notification:
needs: build
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
+2 -2
View File
@@ -1,4 +1,4 @@
name: Daily security audit
name: nightly-security-audit
on:
schedule:
@@ -26,7 +26,7 @@ jobs:
path: .github/workflows/support-files/notifications/deny.message
notification:
needs: cargo-deny
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Check out repository code
uses: actions/checkout@v2
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [macos-latest]
platform: [macos-12-large]
runs-on: ${{ matrix.platform }}
outputs:
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [macos-latest]
platform: [macos-12-large]
runs-on: ${{ matrix.platform }}
outputs:
@@ -1,4 +1,4 @@
name: Nyms5 Android
name: publish-nyms5-android-apk
# unsigned APKs only, supported archs:
# - arm64-v8a (arm64)
# - x86_64
@@ -94,7 +94,7 @@ jobs:
gh-release:
name: Publish APK (GH release)
needs: build
runs-on: custom-runner-linux
runs-on: custom-linux
steps:
- name: Checkout
uses: actions/checkout@v3
+4 -1
View File
@@ -4,7 +4,7 @@ on:
jobs:
publish:
runs-on: [custom-ubuntu-20.04]
runs-on: ubuntu-20.04-16-core
steps:
- uses: actions/checkout@v2
@@ -25,6 +25,9 @@ jobs:
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
- name: Install dependencies
run: yarn
Generated
+5 -5
View File
@@ -3005,7 +3005,7 @@ dependencies = [
[[package]]
name = "extension-storage"
version = "1.2.0-rc.10"
version = "1.2.0"
dependencies = [
"bip39",
"console_error_panic_hook",
@@ -5550,7 +5550,7 @@ dependencies = [
[[package]]
name = "mix-fetch-wasm"
version = "1.2.0-rc.10"
version = "1.2.0"
dependencies = [
"futures",
"js-sys",
@@ -6246,7 +6246,7 @@ dependencies = [
[[package]]
name = "nym-client-wasm"
version = "1.2.0-rc.10"
version = "1.2.0"
dependencies = [
"anyhow",
"futures",
@@ -6875,7 +6875,7 @@ dependencies = [
[[package]]
name = "nym-node-tester-wasm"
version = "1.2.0-rc.10"
version = "1.2.0"
dependencies = [
"futures",
"js-sys",
@@ -7469,7 +7469,7 @@ dependencies = [
[[package]]
name = "nym-wasm-sdk"
version = "1.2.0-rc.10"
version = "1.2.0"
dependencies = [
"mix-fetch-wasm",
"nym-client-wasm",
@@ -74,7 +74,7 @@ impl PartiallyDelegated {
fn route_socket_messages(
ws_msgs: Vec<Message>,
packet_router: &mut PacketRouter,
packet_router: &PacketRouter,
shared_key: &SharedKeys,
) -> Result<(), GatewayClientError> {
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key);
@@ -97,7 +97,6 @@ impl PartiallyDelegated {
let mixnet_receiver_future = async move {
let mut notify_receiver = notify_receiver;
let mut chunk_stream = (&mut stream).ready_chunks(8);
let mut packet_router = packet_router;
let ret_err = loop {
tokio::select! {
@@ -115,7 +114,7 @@ impl PartiallyDelegated {
Ok(msgs) => msgs
};
if let Err(err) = Self::route_socket_messages(ws_msgs, &mut packet_router, shared_key.as_ref()) {
if let Err(err) = Self::route_socket_messages(ws_msgs, &packet_router, shared_key.as_ref()) {
log::warn!("Route socket messages failed: {err}");
}
}
@@ -42,7 +42,9 @@ pub trait GatewayPacketRouter {
}
n if n
== PacketSize::OutfoxRegularPacket.plaintext_size() - outfox_ack_overhead =>
== PacketSize::OutfoxRegularPacket
.plaintext_size()
.saturating_sub(outfox_ack_overhead) =>
{
trace!("received regular outfox packet");
received_messages.push(received_packet);
+52
View File
@@ -0,0 +1,52 @@
use std::net::SocketAddr;
use boringtun::x25519;
use dashmap::{
mapref::one::{Ref, RefMut},
DashMap,
};
use tokio::sync::mpsc::{self};
use crate::event::Event;
pub(crate) type PeersByKey = DashMap<x25519::PublicKey, mpsc::UnboundedSender<Event>>;
pub(crate) type PeersByAddr = DashMap<SocketAddr, mpsc::UnboundedSender<Event>>;
#[derive(Default)]
pub(crate) struct ActivePeers {
active_peers: PeersByKey,
active_peers_by_addr: PeersByAddr,
}
impl ActivePeers {
pub(crate) fn remove(&self, public_key: &x25519::PublicKey) {
log::info!("Removing peer: {public_key:?}");
self.active_peers.remove(public_key);
log::warn!("TODO: remove from peers_by_ip?");
log::warn!("TODO: remove from peers_by_addr");
}
pub(crate) fn insert(
&self,
public_key: x25519::PublicKey,
addr: SocketAddr,
peer_tx: mpsc::UnboundedSender<Event>,
) {
self.active_peers.insert(public_key, peer_tx.clone());
self.active_peers_by_addr.insert(addr, peer_tx);
}
pub(crate) fn get_by_key_mut(
&self,
public_key: &x25519::PublicKey,
) -> Option<RefMut<'_, x25519::PublicKey, mpsc::UnboundedSender<Event>>> {
self.active_peers.get_mut(public_key)
}
pub(crate) fn get_by_addr(
&self,
addr: &SocketAddr,
) -> Option<Ref<'_, SocketAddr, mpsc::UnboundedSender<Event>>> {
self.active_peers_by_addr.get(addr)
}
}
+2
View File
@@ -4,4 +4,6 @@ use thiserror::Error;
pub enum WgError {
#[error("unable to get tunnel")]
UnableToGetTunnel,
#[error("handshake failed")]
HandshakeFailed,
}
+15 -9
View File
@@ -3,25 +3,31 @@ use std::fmt::{Display, Formatter};
use bytes::Bytes;
#[allow(unused)]
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum Event {
/// IP packet received from the WireGuard tunnel that should be passed through to the corresponding virtual device/internet.
/// Original implementation also has protocol here since it understands it, but we'll have to infer it downstream
WgPacket(Bytes),
/// IP packet received from the WireGuard tunnel that should be passed through to the
/// corresponding virtual device/internet.
Wg(Bytes),
/// IP packet received from the WireGuard tunnel that was verified as part of the handshake.
WgVerified(Bytes),
/// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device.
IpPacket(Bytes),
Ip(Bytes),
}
impl Display for Event {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Event::WgPacket(data) => {
Event::Wg(data) => {
let size = data.len();
write!(f, "WgPacket{{ size={size} }}")
write!(f, "Wg{{ size={size} }}")
}
Event::IpPacket(data) => {
Event::WgVerified(data) => {
let size = data.len();
write!(f, "IpPacket{{ size={size} }}")
write!(f, "WgVerified{{ size={size} }}")
}
Event::Ip(data) => {
let size = data.len();
write!(f, "Ip{{ size={size} }}")
}
}
}
+15 -5
View File
@@ -1,9 +1,14 @@
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
// #![warn(clippy::pedantic)]
// #![warn(clippy::expect_used)]
// #![warn(clippy::unwrap_used)]
mod active_peers;
mod error;
mod event;
mod network_table;
mod platform;
mod registered_peers;
mod setup;
mod udp_listener;
mod wg_tunnel;
@@ -13,7 +18,7 @@ mod wg_tunnel;
use platform::linux::tun_device;
#[derive(Clone)]
struct TunTaskTx(tokio::sync::mpsc::UnboundedSender<Vec<u8>>);
pub struct TunTaskTx(tokio::sync::mpsc::UnboundedSender<Vec<u8>>);
impl TunTaskTx {
fn send(&self, packet: Vec<u8>) -> Result<(), tokio::sync::mpsc::error::SendError<Vec<u8>>> {
@@ -21,21 +26,26 @@ impl TunTaskTx {
}
}
/// Start wireguard UDP listener and TUN device
///
/// # Errors
///
/// This function will return an error if either the UDP listener of the TUN device fails to start.
#[cfg(target_os = "linux")]
pub async fn start_wireguard(
task_client: nym_task::TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
use std::sync::Arc;
// The set of active tunnels indexed by the peer's address
let active_peers = Arc::new(udp_listener::ActivePeers::new());
let peers_by_ip = Arc::new(std::sync::Mutex::new(network_table::NetworkTable::new()));
// Start the tun device that is used to relay traffic outbound
let tun_task_tx = tun_device::start_tun_device(peers_by_ip.clone());
let (tun, tun_task_tx) = tun_device::TunDevice::new(peers_by_ip.clone());
tun.start();
// Start the UDP listener that clients connect to
udp_listener::start_udp_listener(tun_task_tx, active_peers, peers_by_ip, task_client).await?;
let udp_listener = udp_listener::WgUdpListener::new(tun_task_tx, peers_by_ip).await?;
udp_listener.start(task_client);
Ok(())
}
+1 -1
View File
@@ -4,7 +4,7 @@ use ip_network::IpNetwork;
use ip_network_table::IpNetworkTable;
#[derive(Default)]
pub(crate) struct NetworkTable<T> {
pub struct NetworkTable<T> {
ips: IpNetworkTable<T>,
}
@@ -28,72 +28,111 @@ fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> t
.expect("Failed to setup tun device, do you have permission?")
}
pub(crate) fn start_tun_device(peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>) -> TunTaskTx {
let tun = setup_tokio_tun_device(
format!("{}%d", TUN_BASE_NAME).as_str(),
TUN_DEVICE_ADDRESS.parse().unwrap(),
TUN_DEVICE_NETMASK.parse().unwrap(),
);
log::info!("Created TUN device: {}", tun.name());
pub struct TunDevice {
// The TUN device that we read/write to, to send/receive packets
tun: tokio_tun::Tun,
let (mut tun_device_rx, mut tun_device_tx) = tokio::io::split(tun);
// Incoming data that we should send
tun_task_rx: mpsc::UnboundedReceiver<Vec<u8>>,
// Channels to communicate with the other tasks
let (tun_task_tx, mut tun_task_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let tun_task_tx = TunTaskTx(tun_task_tx);
// The routing table.
// An alternative would be to do NAT by just matching incoming with outgoing.
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
}
tokio::spawn(async move {
impl TunDevice {
pub fn new(peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>) -> (Self, TunTaskTx) {
let tun = setup_tokio_tun_device(
format!("{TUN_BASE_NAME}%d").as_str(),
TUN_DEVICE_ADDRESS.parse().unwrap(),
TUN_DEVICE_NETMASK.parse().unwrap(),
);
log::info!("Created TUN device: {}", tun.name());
// Channels to communicate with the other tasks
let (tun_task_tx, tun_task_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let tun_task_tx = TunTaskTx(tun_task_tx);
let tun_device = TunDevice {
tun_task_rx,
tun,
peers_by_ip,
};
(tun_device, tun_task_tx)
}
fn handle_tun_read(&self, packet: &[u8]) {
let dst_addr = boringtun::noise::Tunn::dst_address(packet).unwrap();
let headers = SlicedPacket::from_ip(packet).unwrap();
let src_addr = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => ip.source_addr().to_string(),
InternetSlice::Ipv6(ip, _) => ip.source_addr().to_string(),
};
log::info!(
"iface: read Packet({src_addr} -> {dst_addr}, {} bytes)",
packet.len(),
);
// Route packet to the correct peer.
if let Some(peer_tx) = self
.peers_by_ip
.lock()
.unwrap()
.longest_match(dst_addr)
.map(|(_, tx)| tx)
{
log::info!("Forward packet to wg tunnel");
peer_tx
.send(Event::Ip(packet.to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
} else {
log::info!("No peer found, packet dropped");
}
}
async fn handle_tun_write(&mut self, data: Vec<u8>) {
let headers = SlicedPacket::from_ip(&data).unwrap();
let (source_addr, destination_addr) = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => (ip.source_addr(), ip.destination_addr()),
InternetSlice::Ipv6(_, _) => unimplemented!(),
};
log::info!(
"iface: write Packet({source_addr} -> {destination_addr}, {} bytes)",
data.len()
);
self.tun.write_all(&data).await.unwrap();
}
pub async fn run(mut self) {
let mut buf = [0u8; 1024];
loop {
tokio::select! {
// Reading from the TUN device
len = tun_device_rx.read(&mut buf) => match len {
len = self.tun.read(&mut buf) => match len {
Ok(len) => {
let packet = &buf[..len];
let dst_addr = boringtun::noise::Tunn::dst_address(packet).unwrap();
let headers = SlicedPacket::from_ip(packet).unwrap();
let src_addr = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => ip.source_addr().to_string(),
InternetSlice::Ipv6(ip, _) => ip.source_addr().to_string(),
};
log::info!("iface: read Packet({src_addr} -> {dst_addr}, {len} bytes)");
// Route packet to the correct peer.
if let Some(peer_tx) = peers_by_ip.lock().unwrap().longest_match(dst_addr).map(|(_, tx)| tx) {
log::info!("Forward packet to wg tunnel");
peer_tx
.send(Event::IpPacket(packet.to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
} else {
log::info!("No peer found, packet dropped");
}
self.handle_tun_read(packet);
},
Err(err) => {
log::info!("iface: read error: {err}");
break;
}
},
// Writing to the TUN device
Some(data) = tun_task_rx.recv() => {
let headers = SlicedPacket::from_ip(&data).unwrap();
let (source_addr, destination_addr) = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => (ip.source_addr(), ip.destination_addr()),
InternetSlice::Ipv6(_, _) => unimplemented!(),
};
log::info!(
"iface: write Packet({source_addr} -> {destination_addr}, {} bytes)",
data.len()
);
// log::info!("iface: writing {} bytes", data.len());
tun_device_tx.write_all(&data).await.unwrap();
Some(data) = self.tun_task_rx.recv() => {
self.handle_tun_write(data).await;
}
}
}
log::info!("TUN device shutting down");
});
tun_task_tx
}
pub fn start(self) {
tokio::spawn(async move { self.run().await });
}
}
+56
View File
@@ -0,0 +1,56 @@
use std::{collections::HashMap, sync::Arc};
use boringtun::x25519;
use ip_network::IpNetwork;
pub(crate) type PeerIdx = u32;
#[derive(Debug)]
pub(crate) struct RegisteredPeer {
pub(crate) public_key: x25519::PublicKey,
pub(crate) index: PeerIdx,
pub(crate) allowed_ips: IpNetwork,
// endpoint: SocketAddr,
}
#[derive(Debug, Default)]
pub(crate) struct RegisteredPeers {
peers: HashMap<x25519::PublicKey, Arc<tokio::sync::Mutex<RegisteredPeer>>>,
peers_by_idx: HashMap<PeerIdx, Arc<tokio::sync::Mutex<RegisteredPeer>>>,
}
impl RegisteredPeers {
pub(crate) async fn insert(
&mut self,
public_key: x25519::PublicKey,
peer: Arc<tokio::sync::Mutex<RegisteredPeer>>,
) {
let peer_idx = { peer.lock().await.index };
self.peers.insert(public_key, Arc::clone(&peer));
self.peers_by_idx.insert(peer_idx, peer);
}
#[allow(unused)]
pub(crate) async fn remove(&mut self, public_key: &x25519::PublicKey) {
if let Some(peer) = self.peers.remove(public_key) {
let peer_idx = peer.lock().await.index;
if self.peers_by_idx.remove(&peer_idx).is_none() {
log::error!("Removed registered peer but no registered index was found");
}
}
}
pub(crate) fn get_by_key(
&self,
public_key: &x25519::PublicKey,
) -> Option<&Arc<tokio::sync::Mutex<RegisteredPeer>>> {
self.peers.get(public_key)
}
pub(crate) fn get_by_idx(
&self,
peer_idx: PeerIdx,
) -> Option<&Arc<tokio::sync::Mutex<RegisteredPeer>>> {
self.peers_by_idx.get(&peer_idx)
}
}
+1 -1
View File
@@ -50,7 +50,7 @@ pub fn peer_static_public_key() -> x25519::PublicKey {
let peer_static_public_bytes: [u8; 32] = decode_base64_key(PEER);
let peer_static_public = x25519::PublicKey::try_from(peer_static_public_bytes).unwrap();
info!(
"peer public key: {}",
"Adding wg peer public key: {}",
general_purpose::STANDARD.encode(peer_static_public)
);
peer_static_public
+184 -47
View File
@@ -1,6 +1,9 @@
use std::{net::SocketAddr, sync::Arc};
use std::{net::SocketAddr, sync::Arc, time::Duration};
use dashmap::DashMap;
use boringtun::{
noise::{self, handshake::parse_handshake_anon, rate_limiter::RateLimiter, TunnResult},
x25519,
};
use futures::StreamExt;
use log::error;
use nym_task::TaskClient;
@@ -11,51 +14,110 @@ use tokio::{
};
use crate::{
active_peers::ActivePeers,
error::WgError,
event::Event,
network_table::NetworkTable,
registered_peers::{RegisteredPeer, RegisteredPeers},
setup::{self, WG_ADDRESS, WG_PORT},
TunTaskTx,
};
const MAX_PACKET: usize = 65535;
pub(crate) type ActivePeers = DashMap<SocketAddr, mpsc::UnboundedSender<Event>>;
// Registered peers
pub(crate) type PeersByIp = NetworkTable<mpsc::UnboundedSender<Event>>;
pub(crate) async fn start_udp_listener(
tun_task_tx: TunTaskTx,
active_peers: Arc<ActivePeers>,
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
mut task_client: TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let wg_address = SocketAddr::new(WG_ADDRESS.parse().unwrap(), WG_PORT);
log::info!("Starting wireguard UDP listener on {wg_address}");
let udp_socket = Arc::new(UdpSocket::bind(wg_address).await?);
// Setup some static keys for development
let static_private = setup::server_static_private_key();
async fn add_test_peer(registered_peers: &mut RegisteredPeers) {
let peer_static_public = setup::peer_static_public_key();
let peer_index = 0;
let peer_allowed_ips = setup::peer_allowed_ips();
let test_peer = Arc::new(tokio::sync::Mutex::new(RegisteredPeer {
public_key: peer_static_public,
index: peer_index,
allowed_ips: peer_allowed_ips,
}));
registered_peers.insert(peer_static_public, test_peer).await;
}
tokio::spawn(async move {
pub struct WgUdpListener {
// Our private key
static_private: x25519::StaticSecret,
// Our public key
static_public: x25519::PublicKey,
// The list of registered peers that we allow
registered_peers: RegisteredPeers,
// The routing table, as defined by wireguard
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
// The UDP socket to the peer
udp: Arc<UdpSocket>,
// Send data to the TUN device for sending
tun_task_tx: TunTaskTx,
// Wireguard rate limiter
rate_limiter: RateLimiter,
}
impl WgUdpListener {
pub async fn new(
tun_task_tx: TunTaskTx,
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync + 'static>> {
let wg_address = SocketAddr::new(WG_ADDRESS.parse().unwrap(), WG_PORT);
log::info!("Starting wireguard UDP listener on {wg_address}");
let udp = Arc::new(UdpSocket::bind(wg_address).await?);
// Setup our own keys
let static_private = setup::server_static_private_key();
let static_public = x25519::PublicKey::from(&static_private);
let handshake_max_rate = 100u64;
let rate_limiter = RateLimiter::new(&static_public, handshake_max_rate);
// Create a test peer for dev
let mut registered_peers = RegisteredPeers::default();
add_test_peer(&mut registered_peers).await;
Ok(Self {
static_private,
static_public,
registered_peers,
peers_by_ip,
udp,
tun_task_tx,
rate_limiter,
})
}
pub async fn run(self, mut task_client: TaskClient) {
// The set of active tunnels
let active_peers = ActivePeers::default();
// Each tunnel is run in its own task, and the task handle is stored here so we can remove
// it from `active_peers` when the tunnel is closed
let mut active_peers_task_handles = futures::stream::FuturesUnordered::new();
let mut buf = [0u8; MAX_PACKET];
let mut dst_buf = [0u8; MAX_PACKET];
while !task_client.is_shutdown() {
tokio::select! {
_ = task_client.recv() => {
() = task_client.recv() => {
log::trace!("WireGuard UDP listener: received shutdown");
break;
}
// Reset the rate limiter every 1 sec
() = tokio::time::sleep(Duration::from_secs(1)) => {
self.rate_limiter.reset_count();
},
// Handle tunnel closing
Some(addr) = active_peers_task_handles.next() => {
match addr {
Ok(addr) => {
log::info!("Removing peer: {addr:?}");
active_peers.remove(&addr);
// TODO: remove from peers_by_ip
Some(public_key) = active_peers_task_handles.next() => {
match public_key {
Ok(public_key) => {
active_peers.remove(&public_key);
}
Err(err) => {
error!("WireGuard UDP listener: error receiving shutdown from peer: {err}");
@@ -63,47 +125,122 @@ pub(crate) async fn start_udp_listener(
}
},
// Handle incoming packets
Ok((len, addr)) = udp_socket.recv_from(&mut buf) => {
Ok((len, addr)) = self.udp.recv_from(&mut buf) => {
log::trace!("udp: received {} bytes from {}", len, addr);
if let Some(peer_tx) = active_peers.get_mut(&addr) {
// If this addr has already been encountered, send directly to tunnel
// TODO: optimization opportunity to instead create a connected UDP socket
// inside the wg tunnel, where you can recv the data directly.
if let Some(peer_tx) = active_peers.get_by_addr(&addr) {
log::info!("udp: received {len} bytes from {addr} from known peer");
peer_tx.send(Event::WgPacket(buf[..len].to_vec().into()))
peer_tx
.send(Event::Wg(buf[..len].to_vec().into()))
.tap_err(|e| log::error!("{e}"))
.ok();
continue;
}
// Verify the incoming packet
let verified_packet = match self.rate_limiter.verify_packet(Some(addr.ip()), &buf[..len], &mut dst_buf) {
Ok(packet) => packet,
Err(TunnResult::WriteToNetwork(cookie)) => {
log::info!("Send back cookie to: {addr}");
self.udp.send_to(cookie, addr).await.tap_err(|e| log::error!("{e}")).ok();
continue;
}
Err(err) => {
log::warn!("{err:?}");
continue;
}
};
// Check if this is a registered peer, if not, just skip
let registered_peer = match parse_peer(
verified_packet,
&self.registered_peers,
&self.static_private,
&self.static_public
) {
Ok(Some(peer)) => peer.lock().await,
Ok(None) => {
log::warn!("Peer not registered: {addr}");
continue;
}
Err(err) => {
log::error!("{err}");
continue;
},
};
// Look up if the peer is already connected
if let Some(peer_tx) = active_peers.get_by_key_mut(&registered_peer.public_key) {
// We found the peer as connected, even though the addr was not known
log::info!("udp: received {len} bytes from {addr} which is a known peer with unknown addr");
peer_tx.send(Event::WgVerified(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
.ok();
} else {
// If it isn't, start a new tunnel
log::info!("udp: received {len} bytes from {addr} from unknown peer, starting tunnel");
// TODO: this is a temporary solution for development since this
// assumes we know the peer_static_public this corresponds to.
// TODO: rework this before production! This is likely not secure!
log::warn!("Assuming peer_static_public is known");
log::warn!("SECURITY: Rework me to do proper handshake before creating the tunnel!");
// NOTE: we are NOT passing in the existing rate_limiter. Re-visit this
// choice later.
log::warn!("Creating new rate limiter, consider re-using?");
let (join_handle, peer_tx) = crate::wg_tunnel::start_wg_tunnel(
addr,
udp_socket.clone(),
static_private.clone(),
peer_static_public,
peer_allowed_ips,
tun_task_tx.clone(),
self.udp.clone(),
self.static_private.clone(),
registered_peer.public_key,
registered_peer.index,
registered_peer.allowed_ips,
self.tun_task_tx.clone(),
);
peers_by_ip.lock().unwrap().insert(peer_allowed_ips, peer_tx.clone());
self.peers_by_ip.lock().unwrap().insert(registered_peer.allowed_ips, peer_tx.clone());
peer_tx.send(Event::WgPacket(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
peer_tx.send(Event::Wg(buf[..len].to_vec().into()))
.tap_err(|e| log::error!("{e}"))
.ok();
// WIP(JON): active peers should probably be keyed by peer_static_public
// instead. Does this current setup lead to any issues?
log::info!("Adding peer: {addr}");
active_peers.insert(addr, peer_tx);
log::info!("Adding peer: {:?}: {addr}", registered_peer.public_key);
active_peers.insert(registered_peer.public_key, addr, peer_tx);
active_peers_task_handles.push(join_handle);
}
},
}
}
log::info!("WireGuard listener: shutting down");
});
}
Ok(())
pub fn start(self, task_client: TaskClient) {
tokio::spawn(async move { self.run(task_client).await });
}
}
fn parse_peer<'a>(
verified_packet: noise::Packet,
registered_peers: &'a RegisteredPeers,
static_private: &x25519::StaticSecret,
static_public: &x25519::PublicKey,
) -> Result<Option<&'a Arc<tokio::sync::Mutex<RegisteredPeer>>>, WgError> {
let registered_peer = match verified_packet {
noise::Packet::HandshakeInit(ref packet) => {
let Ok(handshake) = parse_handshake_anon(static_private, static_public, packet) else {
return Err(WgError::HandshakeFailed);
};
registered_peers.get_by_key(&x25519::PublicKey::from(handshake.peer_static_public))
}
noise::Packet::HandshakeResponse(packet) => {
let peer_idx = packet.receiver_idx >> 8;
registered_peers.get_by_idx(peer_idx)
}
noise::Packet::PacketCookieReply(packet) => {
let peer_idx = packet.receiver_idx >> 8;
registered_peers.get_by_idx(peer_idx)
}
noise::Packet::PacketData(packet) => {
let peer_idx = packet.receiver_idx >> 8;
registered_peers.get_by_idx(peer_idx)
}
};
Ok(registered_peer)
}
+34 -14
View File
@@ -2,7 +2,7 @@ use std::{net::SocketAddr, sync::Arc, time::Duration};
use async_recursion::async_recursion;
use boringtun::{
noise::{errors::WireGuardError, Tunn, TunnResult},
noise::{errors::WireGuardError, rate_limiter::RateLimiter, Tunn, TunnResult},
x25519,
};
use bytes::Bytes;
@@ -14,7 +14,11 @@ use tokio::{
time::timeout,
};
use crate::{error::WgError, event::Event, network_table::NetworkTable, TunTaskTx};
use crate::{
error::WgError, event::Event, network_table::NetworkTable, registered_peers::PeerIdx, TunTaskTx,
};
const HANDSHAKE_MAX_RATE: u64 = 10;
const MAX_PACKET: usize = 65535;
@@ -55,7 +59,9 @@ impl WireGuardTunnel {
endpoint: SocketAddr,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
index: PeerIdx,
peer_allowed_ips: ip_network::IpNetwork,
// rate_limiter: Option<RateLimiter>,
tunnel_tx: TunTaskTx,
) -> (Self, mpsc::UnboundedSender<Event>) {
let local_addr = udp.local_addr().unwrap();
@@ -64,8 +70,12 @@ impl WireGuardTunnel {
let preshared_key = None;
let persistent_keepalive = None;
let index = 0;
let rate_limiter = None;
let static_public = x25519::PublicKey::from(&static_private);
let rate_limiter = Some(Arc::new(RateLimiter::new(
&static_public,
HANDSHAKE_MAX_RATE,
)));
let wg_tunnel = Arc::new(tokio::sync::Mutex::new(
Tunn::new(
@@ -117,12 +127,17 @@ impl WireGuardTunnel {
Some(packet) => {
info!("event loop: {packet}");
match packet {
Event::WgPacket(data) => {
Event::Wg(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,
Event::WgVerified(data) => {
let _ = self.consume_verified_wg(&data)
.await
.tap_err(|err| error!("WireGuard tunnel: consume_verified_wg error: {err}"));
}
Event::Ip(data) => self.consume_eth(&data).await,
}
},
None => {
@@ -130,7 +145,7 @@ impl WireGuardTunnel {
break;
},
},
_ = tokio::time::sleep(Duration::from_millis(250)) => {
() = tokio::time::sleep(Duration::from_millis(250)) => {
let _ = self.update_wg_timers()
.await
.map_err(|err| error!("WireGuard tunnel: update_wg_timers error: {err}"));
@@ -182,8 +197,6 @@ impl WireGuardTunnel {
}
}
TunnResult::WriteToTunnelV4(packet, addr) => {
// TODO: once the flow is redone, we should add updating the endpoint dynamically
// self.set_endpoint(addr);
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
} else {
@@ -191,8 +204,6 @@ impl WireGuardTunnel {
}
}
TunnResult::WriteToTunnelV6(packet, addr) => {
// TODO: once the flow is redone, we should add updating the endpoint dynamically
// self.set_endpoint(addr);
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
} else {
@@ -209,6 +220,13 @@ impl WireGuardTunnel {
Ok(())
}
async fn consume_verified_wg(&mut self, data: &[u8]) -> Result<(), WgError> {
// Potentially we could take some shortcuts here in the name of performance, but currently
// I don't see that the needed functions in boringtun is exposed in the public API.
// TODO: make sure we don't put double pressure on the rate limiter!
self.consume_wg(data).await
}
async fn consume_eth(&self, data: &Bytes) {
info!("consume_eth: raw packet size: {}", data.len());
let encapsulated_packet = self.encapsulate_packet(data).await;
@@ -269,7 +287,7 @@ impl WireGuardTunnel {
return;
};
peer.format_handshake_initiation(&mut buf[..], false);
self.handle_routine_tun_result(result).await
self.handle_routine_tun_result(result).await;
}
TunnResult::Err(err) => {
error!("Failed to prepare routine packet for WireGuard endpoint: {err:?}");
@@ -287,10 +305,11 @@ pub(crate) fn start_wg_tunnel(
udp: Arc<UdpSocket>,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
peer_index: PeerIdx,
peer_allowed_ips: ip_network::IpNetwork,
tunnel_tx: TunTaskTx,
) -> (
tokio::task::JoinHandle<SocketAddr>,
tokio::task::JoinHandle<x25519::PublicKey>,
mpsc::UnboundedSender<Event>,
) {
let (mut tunnel, peer_tx) = WireGuardTunnel::new(
@@ -298,12 +317,13 @@ pub(crate) fn start_wg_tunnel(
endpoint,
static_private,
peer_static_public,
peer_index,
peer_allowed_ips,
tunnel_tx,
);
let join_handle = tokio::spawn(async move {
tunnel.spin_off().await;
endpoint
peer_static_public
});
(join_handle, peer_tx)
}
+2 -2
View File
@@ -43,7 +43,7 @@ turn-off = true
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.0.0" # do not edit: managed by `mdbook-admonish install`
# variables preprocessor: import variables into files
# https://gitlab.com/tglman/mdbook-variables/
@@ -76,7 +76,7 @@ curly-quotes = true
# mathjax-support = false # useful if we want to pull equations in ?
copy-fonts = true
no-section-label = false
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "custom.css"]
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "custom.css", "./mdbook-admonish.css"]
additional-js = ["theme/pagetoc.js"]
git-repository-url = "https://github.com/nymtech/nym"
git-repository-icon = "fa-github"
+81 -92
View File
@@ -1,31 +1,18 @@
@charset "UTF-8";
:root {
--md-admonition-icon--note:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--abstract:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--info:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--tip:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--success:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--question:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--warning:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--failure:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--danger:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--bug:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--example:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--quote:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
:is(.admonition) {
@@ -75,8 +62,9 @@ a.admonition-anchor-link::before {
content: "§";
}
:is(.admonition-title, summary) {
:is(.admonition-title, summary.admonition-title) {
position: relative;
min-height: 4rem;
margin-block: 0;
margin-inline: -1.6rem -1.2rem;
padding-block: 0.8rem;
@@ -85,13 +73,13 @@ a.admonition-anchor-link::before {
background-color: rgba(68, 138, 255, 0.1);
display: flex;
}
:is(.admonition-title, summary) p {
:is(.admonition-title, summary.admonition-title) p {
margin: 0;
}
html :is(.admonition-title, summary):last-child {
html :is(.admonition-title, summary.admonition-title):last-child {
margin-bottom: 0;
}
:is(.admonition-title, summary)::before {
:is(.admonition-title, summary.admonition-title)::before {
position: absolute;
top: 0.625em;
inset-inline-start: 1.6rem;
@@ -106,7 +94,7 @@ html :is(.admonition-title, summary):last-child {
-webkit-mask-size: contain;
content: "";
}
:is(.admonition-title, summary):hover a.admonition-anchor-link {
:is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
display: initial;
}
@@ -131,204 +119,204 @@ details[open].admonition > summary.admonition-title::after {
transform: rotate(90deg);
}
:is(.admonition):is(.note) {
:is(.admonition):is(.admonish-note) {
border-color: #448aff;
}
:is(.note) > :is(.admonition-title, summary) {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.note) > :is(.admonition-title, summary)::before {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--note);
-webkit-mask-image: var(--md-admonition-icon--note);
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.abstract, .summary, .tldr) {
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
border-color: #00b0ff;
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 176, 255, 0.1);
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b0ff;
mask-image: var(--md-admonition-icon--abstract);
-webkit-mask-image: var(--md-admonition-icon--abstract);
mask-image: var(--md-admonition-icon--admonish-abstract);
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.info, .todo) {
:is(.admonition):is(.admonish-info, .admonish-todo) {
border-color: #00b8d4;
}
:is(.info, .todo) > :is(.admonition-title, summary) {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 184, 212, 0.1);
}
:is(.info, .todo) > :is(.admonition-title, summary)::before {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b8d4;
mask-image: var(--md-admonition-icon--info);
-webkit-mask-image: var(--md-admonition-icon--info);
mask-image: var(--md-admonition-icon--admonish-info);
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.tip, .hint, .important) {
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
border-color: #00bfa5;
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary) {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 191, 165, 0.1);
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00bfa5;
mask-image: var(--md-admonition-icon--tip);
-webkit-mask-image: var(--md-admonition-icon--tip);
mask-image: var(--md-admonition-icon--admonish-tip);
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.success, .check, .done) {
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
border-color: #00c853;
}
:is(.success, .check, .done) > :is(.admonition-title, summary) {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 200, 83, 0.1);
}
:is(.success, .check, .done) > :is(.admonition-title, summary)::before {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00c853;
mask-image: var(--md-admonition-icon--success);
-webkit-mask-image: var(--md-admonition-icon--success);
mask-image: var(--md-admonition-icon--admonish-success);
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.question, .help, .faq) {
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
border-color: #64dd17;
}
:is(.question, .help, .faq) > :is(.admonition-title, summary) {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(100, 221, 23, 0.1);
}
:is(.question, .help, .faq) > :is(.admonition-title, summary)::before {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #64dd17;
mask-image: var(--md-admonition-icon--question);
-webkit-mask-image: var(--md-admonition-icon--question);
mask-image: var(--md-admonition-icon--admonish-question);
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.warning, .caution, .attention) {
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
border-color: #ff9100;
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary) {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 145, 0, 0.1);
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff9100;
mask-image: var(--md-admonition-icon--warning);
-webkit-mask-image: var(--md-admonition-icon--warning);
mask-image: var(--md-admonition-icon--admonish-warning);
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.failure, .fail, .missing) {
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
border-color: #ff5252;
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary) {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 82, 82, 0.1);
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff5252;
mask-image: var(--md-admonition-icon--failure);
-webkit-mask-image: var(--md-admonition-icon--failure);
mask-image: var(--md-admonition-icon--admonish-failure);
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.danger, .error) {
:is(.admonition):is(.admonish-danger, .admonish-error) {
border-color: #ff1744;
}
:is(.danger, .error) > :is(.admonition-title, summary) {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 23, 68, 0.1);
}
:is(.danger, .error) > :is(.admonition-title, summary)::before {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff1744;
mask-image: var(--md-admonition-icon--danger);
-webkit-mask-image: var(--md-admonition-icon--danger);
mask-image: var(--md-admonition-icon--admonish-danger);
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.bug) {
:is(.admonition):is(.admonish-bug) {
border-color: #f50057;
}
:is(.bug) > :is(.admonition-title, summary) {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(245, 0, 87, 0.1);
}
:is(.bug) > :is(.admonition-title, summary)::before {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #f50057;
mask-image: var(--md-admonition-icon--bug);
-webkit-mask-image: var(--md-admonition-icon--bug);
mask-image: var(--md-admonition-icon--admonish-bug);
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.example) {
:is(.admonition):is(.admonish-example) {
border-color: #7c4dff;
}
:is(.example) > :is(.admonition-title, summary) {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(124, 77, 255, 0.1);
}
:is(.example) > :is(.admonition-title, summary)::before {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #7c4dff;
mask-image: var(--md-admonition-icon--example);
-webkit-mask-image: var(--md-admonition-icon--example);
mask-image: var(--md-admonition-icon--admonish-example);
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.quote, .cite) {
:is(.admonition):is(.admonish-quote, .admonish-cite) {
border-color: #9e9e9e;
}
:is(.quote, .cite) > :is(.admonition-title, summary) {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(158, 158, 158, 0.1);
}
:is(.quote, .cite) > :is(.admonition-title, summary)::before {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9e9e9e;
mask-image: var(--md-admonition-icon--quote);
-webkit-mask-image: var(--md-admonition-icon--quote);
mask-image: var(--md-admonition-icon--admonish-quote);
-webkit-mask-image: var(--md-admonition-icon--admonish-quote);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
@@ -339,7 +327,8 @@ details[open].admonition > summary.admonition-title::after {
background-color: var(--sidebar-bg);
}
.ayu :is(.admonition), .coal :is(.admonition) {
.ayu :is(.admonition),
.coal :is(.admonition) {
background-color: var(--theme-hover);
}
@@ -17,8 +17,7 @@ This is a *reference page*, to see the entire presentation join Max's talk at [H
## SDKs
* [Rust SDK](https://nymtech.net/docs/sdk/rust.html)
* [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html)
* [Interactive Typescript SDK docs](https://sdk.nymtech.net)
* [Typescript SDK](https://sdk.nymtech.net/)
### Rust examples
+5 -1
View File
@@ -2,4 +2,8 @@
Welcome to the Nym Developer Portal, containing quickstart resources, user manuals, integration information, and tutorials outlining to start building privacy enhanced apps.
For more in-depth information about nodes, network traffic flows, clients, coconut etc check out the [docs](https://nymtech.net/docs). If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
For more in-depth information about nodes, network traffic flows, clients, coconut etc check out the [docs](https://nymtech.net/docs).
If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
If you're looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more, make sure to check out the **new [TS SDK Handbook](https://sdk.nymtech.net/)** !
@@ -1,3 +1,3 @@
# Typescript
Tutorial code in this section is built to interact with a standalone Nym client. You can read about interacting with standalone clients [here](https://nymtech.net/docs/clients/websocket-client.html#connecting-to-the-local-websocket), although it is usually preferable to use the [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html).
Tutorial code in this section is built to interact with a standalone Nym client. You can read about interacting with standalone clients [here](https://nymtech.net/docs/clients/websocket-client.html#connecting-to-the-local-websocket), although it is usually preferable to use the [Typescript SDK](https://sdk.nymtech.net/).
+2 -2
View File
@@ -42,7 +42,7 @@ turn-off = true
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.0.0" # do not edit: managed by `mdbook-admonish install`
# variables preprocessor: import variables into files
# https://gitlab.com/tglman/mdbook-variables/
@@ -83,7 +83,7 @@ curly-quotes = true
# mathjax-support = false # useful if we want to pull equations in
copy-fonts = true
no-section-label = false
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "./custom.css"]
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "./custom.css", "./mdbook-admonish.css"]
additional-js = ["theme/pagetoc.js"]
git-repository-url = "https://github.com/nymtech/nym"
git-repository-icon = "fa-github"
+81 -92
View File
@@ -1,31 +1,18 @@
@charset "UTF-8";
:root {
--md-admonition-icon--note:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--abstract:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--info:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--tip:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--success:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--question:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--warning:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--failure:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--danger:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--bug:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--example:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--quote:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
:is(.admonition) {
@@ -75,8 +62,9 @@ a.admonition-anchor-link::before {
content: "§";
}
:is(.admonition-title, summary) {
:is(.admonition-title, summary.admonition-title) {
position: relative;
min-height: 4rem;
margin-block: 0;
margin-inline: -1.6rem -1.2rem;
padding-block: 0.8rem;
@@ -85,13 +73,13 @@ a.admonition-anchor-link::before {
background-color: rgba(68, 138, 255, 0.1);
display: flex;
}
:is(.admonition-title, summary) p {
:is(.admonition-title, summary.admonition-title) p {
margin: 0;
}
html :is(.admonition-title, summary):last-child {
html :is(.admonition-title, summary.admonition-title):last-child {
margin-bottom: 0;
}
:is(.admonition-title, summary)::before {
:is(.admonition-title, summary.admonition-title)::before {
position: absolute;
top: 0.625em;
inset-inline-start: 1.6rem;
@@ -106,7 +94,7 @@ html :is(.admonition-title, summary):last-child {
-webkit-mask-size: contain;
content: "";
}
:is(.admonition-title, summary):hover a.admonition-anchor-link {
:is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
display: initial;
}
@@ -131,204 +119,204 @@ details[open].admonition > summary.admonition-title::after {
transform: rotate(90deg);
}
:is(.admonition):is(.note) {
:is(.admonition):is(.admonish-note) {
border-color: #448aff;
}
:is(.note) > :is(.admonition-title, summary) {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.note) > :is(.admonition-title, summary)::before {
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--note);
-webkit-mask-image: var(--md-admonition-icon--note);
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.abstract, .summary, .tldr) {
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
border-color: #00b0ff;
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 176, 255, 0.1);
}
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before {
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b0ff;
mask-image: var(--md-admonition-icon--abstract);
-webkit-mask-image: var(--md-admonition-icon--abstract);
mask-image: var(--md-admonition-icon--admonish-abstract);
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.info, .todo) {
:is(.admonition):is(.admonish-info, .admonish-todo) {
border-color: #00b8d4;
}
:is(.info, .todo) > :is(.admonition-title, summary) {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 184, 212, 0.1);
}
:is(.info, .todo) > :is(.admonition-title, summary)::before {
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b8d4;
mask-image: var(--md-admonition-icon--info);
-webkit-mask-image: var(--md-admonition-icon--info);
mask-image: var(--md-admonition-icon--admonish-info);
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.tip, .hint, .important) {
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
border-color: #00bfa5;
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary) {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 191, 165, 0.1);
}
:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before {
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00bfa5;
mask-image: var(--md-admonition-icon--tip);
-webkit-mask-image: var(--md-admonition-icon--tip);
mask-image: var(--md-admonition-icon--admonish-tip);
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.success, .check, .done) {
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
border-color: #00c853;
}
:is(.success, .check, .done) > :is(.admonition-title, summary) {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 200, 83, 0.1);
}
:is(.success, .check, .done) > :is(.admonition-title, summary)::before {
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00c853;
mask-image: var(--md-admonition-icon--success);
-webkit-mask-image: var(--md-admonition-icon--success);
mask-image: var(--md-admonition-icon--admonish-success);
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.question, .help, .faq) {
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
border-color: #64dd17;
}
:is(.question, .help, .faq) > :is(.admonition-title, summary) {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(100, 221, 23, 0.1);
}
:is(.question, .help, .faq) > :is(.admonition-title, summary)::before {
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #64dd17;
mask-image: var(--md-admonition-icon--question);
-webkit-mask-image: var(--md-admonition-icon--question);
mask-image: var(--md-admonition-icon--admonish-question);
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.warning, .caution, .attention) {
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
border-color: #ff9100;
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary) {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 145, 0, 0.1);
}
:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before {
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff9100;
mask-image: var(--md-admonition-icon--warning);
-webkit-mask-image: var(--md-admonition-icon--warning);
mask-image: var(--md-admonition-icon--admonish-warning);
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.failure, .fail, .missing) {
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
border-color: #ff5252;
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary) {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 82, 82, 0.1);
}
:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before {
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff5252;
mask-image: var(--md-admonition-icon--failure);
-webkit-mask-image: var(--md-admonition-icon--failure);
mask-image: var(--md-admonition-icon--admonish-failure);
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.danger, .error) {
:is(.admonition):is(.admonish-danger, .admonish-error) {
border-color: #ff1744;
}
:is(.danger, .error) > :is(.admonition-title, summary) {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 23, 68, 0.1);
}
:is(.danger, .error) > :is(.admonition-title, summary)::before {
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff1744;
mask-image: var(--md-admonition-icon--danger);
-webkit-mask-image: var(--md-admonition-icon--danger);
mask-image: var(--md-admonition-icon--admonish-danger);
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.bug) {
:is(.admonition):is(.admonish-bug) {
border-color: #f50057;
}
:is(.bug) > :is(.admonition-title, summary) {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(245, 0, 87, 0.1);
}
:is(.bug) > :is(.admonition-title, summary)::before {
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #f50057;
mask-image: var(--md-admonition-icon--bug);
-webkit-mask-image: var(--md-admonition-icon--bug);
mask-image: var(--md-admonition-icon--admonish-bug);
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.example) {
:is(.admonition):is(.admonish-example) {
border-color: #7c4dff;
}
:is(.example) > :is(.admonition-title, summary) {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(124, 77, 255, 0.1);
}
:is(.example) > :is(.admonition-title, summary)::before {
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #7c4dff;
mask-image: var(--md-admonition-icon--example);
-webkit-mask-image: var(--md-admonition-icon--example);
mask-image: var(--md-admonition-icon--admonish-example);
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.quote, .cite) {
:is(.admonition):is(.admonish-quote, .admonish-cite) {
border-color: #9e9e9e;
}
:is(.quote, .cite) > :is(.admonition-title, summary) {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(158, 158, 158, 0.1);
}
:is(.quote, .cite) > :is(.admonition-title, summary)::before {
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9e9e9e;
mask-image: var(--md-admonition-icon--quote);
-webkit-mask-image: var(--md-admonition-icon--quote);
mask-image: var(--md-admonition-icon--admonish-quote);
-webkit-mask-image: var(--md-admonition-icon--admonish-quote);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
@@ -339,7 +327,8 @@ details[open].admonition > summary.admonition-title::after {
background-color: var(--sidebar-bg);
}
.ayu :is(.admonition), .coal :is(.admonition) {
.ayu :is(.admonition),
.coal :is(.admonition) {
background-color: var(--theme-hover);
}
+4 -2
View File
@@ -2,9 +2,11 @@
This is Nym's technical documentation, containing information and setup guides about the various pieces of Nym software such as different mixnet infrastructure nodes, application clients, and existing applications like the desktop wallet and mixnet explorer.
If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/) where you can find also our [FAQ section](https://nymtech.net/developers/faq/general-faq.md).
If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/) where you can find also our [FAQ section](https://nymtech.net/developers/faq/general-faq.md).
If you're specically looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more - make sure to check out the **new [TS SDK Handbook](https://sdk.nymtech.net/)** !
## Popular pages
**Network Architecture:**
@@ -12,7 +14,7 @@ If you are new to Nym and want to learn about the mixnet, explore kickstart opti
* [Mixnet Traffic Flow](./architecture/traffic-flow.md)
**SDK examples:**
* [Typescript SDK](./sdk/typescript.md)
* [Typescript SDK](https://sdk.nymtech.net/)
* [Rust SDK](./sdk/rust.md)
**Nyx**
+1 -63
View File
@@ -1,66 +1,4 @@
# Typescript SDK
The Typescript SDK allows developers to start building browser-based mixnet applications quickly, by simply importing the SDK into their code via NPM as they would any other Typescript library.
You can find the source code [here](https://github.com/nymtech/nym/tree/master/sdk) and the library on NPM [here](https://www.npmjs.com/package/@nymproject/sdk).
Currently developers can use the SDK to do the following **entirely in the browser**:
* Create a client
* Listen for incoming messages and reply to them
* Encrypt text and binary-encoded messages as Sphinx packets and send these through the mixnet
> We will be fleshing out further mixnet-related features in the coming weeks with functionality such as importing/exporting keypairs for developing apps with a retained identity over time.
In the future the SDK will be made up of several components, each of which will allow developers to interact with different parts of Nym's infrastructure.
| Component | Functionality | Released |
| --------- | ------------------------------------------------------------------------------ | -------- |
| Mixnet | Create clients & keypairs, subscribe to Mixnet events, send & receive messages | ✔️ |
| Coconut | Create & verify Coconut credentials | ❌ |
| Validator | Sign & broadcast Nyx blockchain transactions, query the blockchain | ❌ |
### How it works
The SDK can be thought of as a 'wrapper' around the compiled [WebAssembly client](https://github.com/nymtech/nym/tree/master/clients/webassembly) code: it runs the client (a Wasm blob) in a web worker. This allows us to keep the work done by the client - such as the heavy lifting of creating and multiply-encrypting Sphinx packets - in a seperate thread from our UI, enabling you to build reactive frontends without worrying about the work done under the hood by the client eating your processing power.
The SDK exposes an interface that allows developers to interact with the Wasm blob inside the webworker from frontend code.
### Framework Support
Currently, the SDK **only** works with frameworks that use either `Webpack` or `Parcel` as bundlers. If you want to use the SDK with a framework that isn't on this list, such as Angular, or NodeJS, **here be dragons!** These frameworks will probably use a different bundler and you will probably run into problems.
| Bundler | Supported |
| ------- | --------- |
| Webpack | ✔️ |
| Packer | ✔️ |
Support for environments with different bundlers will be added in subsequent releases.
| Environment | Supported |
| ---------------- | --------- |
| Browser | ✔️ |
| Headless NodeJS | ❌ |
| Electron Desktop | ❌ |
### Using the SDK
There are multiple example projects in [`nym/sdk/typescript/examples/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/), each for a different frontend framework.
#### Vanilla HTML
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/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.
#### Parcel
If you don't want to use `Webpack` as your app bundler, we have an example with `Parcel` located at [`examples/parcel/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/chat-app/parcel).
#### Create React App
For React developers we have an example which is a basic React app scaffold with the additional logic for creating a client and subscribing to mixnet events in [`examples/react-webpack-with-theme-example/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/chat-app/react-webpack-with-theme-example).
### Developers: think about what you're sending (and importing)!
Think about what information your app sends. That goes for whatever you put into your Sphinx packet messages as well as what your app's environment may leak.
Whenever you write client PEApps using HTML/JavaScript, we recommend that you **do not load external resources from CDNs**. Webapp developers do this all the time, to save load time for common resources, or just for convenience. But when you're writing privacy apps it's better not to make these kinds of requests. **Pack everything locally**.
If you use only local resources within your Electron app or your browser extensions, explicitly encoding request data in a Sphinx packet does protect you from the normal leakage that gets sent in a browser HTTP request. [There's a lot of stuff that leaks when you make an HTTP request from a browser window](https://panopticlick.eff.org/). Luckily, all that metadata and request leakage doesn't happen in Nym, because you're choosing very explicitly what to encode into Sphinx packets, instead of sending a whole browser environment by default.
> If you'd like to learn more, build apps or integrate Nym components using the TS SDK, please visit the **dedicated [TS SDK Handbook](https://sdk.nymtech.net/)** !
+4
View File
@@ -24,6 +24,10 @@
- [Mix Nodes](./faq/mixnodes-faq.md)
- [Project Smoosh](./faq/smoosh-faq.md)
# Legal Forum
- [Exit Gateway](./legal/exit-gateway.md)
---
# Misc.
- [Code of Conduct](coc.md)
@@ -0,0 +1,205 @@
# Nym operators - Running Exit Gateway
```admonish info
The entire content of this page is under [Creative Commons Attribution 4.0 International Public License](https://creativecommons.org/licenses/by/4.0/).
```
This page is a part of Nym Community Legal Forum and its content is composed by shared advices in [Node Operators Legal Forum](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) (Matrix chat) as well as though pull requests done by the node operators directly to our [repository](https://github.com/nymtech/nym/tree/develop/documentation/operators/src), reviewed by Nym DevRels.
This document presents an initiative to further support Nyms mission of allowing privacy for everyone everywhere. This would be achieved with the support of Nym node operators operating gateways and opening these to any online service with the safeguards of the [Tor Null deny list](https://tornull.org/).
```admonish warning
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://discord.com/invite/nym), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
```
## Summary
* This document outlines a plan to change Nym Gateways from operating with an allow to a deny list to enable broader uptake and usage of the Nym mixnet. It provides operators with an overview of the plan, pros and cons, legal as well as technical advice.
* Nym is committed to ensuring privacy for all users, regardless of their location and for the broadest possible range of online services. In order to achieve this aim, the Nym mixnet needs to increase its usability across a broad range of apps and services.
* Currently, Nym Gateway nodes only enable access to apps and services that are on an allow list that is maintained by the core team.
* To decentralise and enable privacy for a broader range of services, this initiative will have to transition from the current allow list to a deny list (based on the [Tor Null advisory BL](https://tornull.org/)).
* This will enhance the usage and appeal of Nym products for end users. As a result, increased usage will ultimately lead to higher revenues for Nym operators.
* Nym core team cannot provide operators with definitive answers regarding the potential risks of operating open Gateways. However, there is online evidence of operating Tor exit relays:
* From a technical perspective, Nym node operators may need to implement additional controls, such as dedicated hardware and IP usage, or setting up an HTML exit notice on port 80.
* From an operational standpoint, node operators may be expected to actively manage their relationship with their ISP or VPS provider and respond to abuse requests using the proposed templates.
* Legally, exit relays are typically considered "telecommunication networks" and are subject to intermediary liability protection. However, there may be exceptions, particularly in cases involving criminal law and copyright claims. Operators could seek advice from local privacy associations and may consider running nodes under an entity rather than as individuals.
* This document serves as the basis for a consultation with Nym node operators on any concerns or additional support and information you need for this change to be successful and ensure maximum availability, usability and adoption.
## Goal of the initiative
**Nym supports privacy for everyone, everywhere.**
To offer a better and more private everyday experience for its users, Nym would like them to use any online services they please, without limiting its access to a few messaging apps or crypto wallets.
To achieve this, operators running “gateways” would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays.
## Pros and cons of the initiative
Previous setup: Running nodes supporting strict SOCKS5 app-based traffic
| **Dimension** | **Pros** | **Cons** |
| :--- | :--- | :--- |
| Aspirational | | - Very limited use cases, not supportive of the “Privacy for everyone everywhere” aspiration<br>- Limited appeal to users, low competitiveness in the market, thus low usage |
| Technical | - No changes required in technical setup | |
| Operational | - No impact on operators operations (e.g., relationships with VPS providers)<br>- Low overhead<br>- Can be run as an individual | |
| Legal | - Limited legal risks for operators | |
| Financial | | - Low revenues for operators due to limited product traction |
The new setup: Running nodes supporting traffic of any online service (with safeguards in the form of an denylist)
| **Dimension** | **Pros** | **Cons** |
| :--- | :--- | :--- |
| Aspirational | - Higher market appeal of a fully-fledged product able to answer all users use cases<br>- Relevance in the market, driving higher usage | |
| Technical | - Very limited changes required in the technical setup (changes in the allow -> denylist) | - Increased monitoring required to detect and prevent abuse (e.g. spam) |
| Operational | | - Higher operational overhead, such as dealing with DMCA / abuse complaints, managing the VPS provider questions, or helping the community to maintain the denylist <br>- Administrative overhead if running nodes as a company or an entity |
| Legal | | - Ideally requires to check legal environment with local privacy association or lawyer | Financial | - Higher revenue potential for operators due to the increase in network usage | - If not running VPS with an unlimited bandwidth plan, higher costs due to higher network usage |
## New gateway setup
In our previous technical setup, network requesters acted as a proxy, and only made requests that match an allow list. That was a default IP based list of allowed domains stored at Nym page in a centralised fashion possibly re-defined by any Network requester operator.
This restricts the hosts that the NymConnect app can connect to and has the effect of selectively supporting messaging services (e.g. Telegram, Matrix) or crypto wallets (e.g. Electrum or Monero). Operators of network requesters can have confidence that the infrastructure they run only connects to a limited set of public internet hosts.
In the new setup, the main change is to expand this short allow list to a more permissive setup. An exit policy will constrain the hosts that the users of the Nym Mixnet and Nym VPN 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 Mixnet VPN and VPN clients (both wrapped in the same app).
As of now we the gateways will be defaulted to Tornulls (note: Not affiliated with Tor) deny list - reproduction permitted under Creative Commons Attribution 3.0 United States License which is IP-based, e.g., `ExitPolicy reject 5.188.10.0/23:*`. Whether we will stick with this list, do modifications (likely) or compile another one is still a subject of discussion.
<:--
These policies will be either reused without modification from Tor / Tornull (license permitting), or customized and updated in a Nym crowd-sourced community effort.
-->
The Gateways will display an HTML page similar to that suggested by [Tor](https://gitlab.torproject.org/tpo/core/tor/-/raw/HEAD/contrib/operator-tools/tor-exit-notice.html) for exit relays on port 80 and port 443. This will allow the operator to provide information about their Gateway, possibly including the currently configured exit policy, without having to actively communicate with law enforcement or regulatory authorities. It also makes the behaviour of the Gateway transparent and even computable (a possible feature would be to offer a machine readable form of the notice in JSON or YAML).
We also recommend operators to check the technical advice from [Tor](https://community.torproject.org/relay/setup/exit/).
## Tor legal advice
Giving the legal similarity between Nym exit gateways and Tor exit relays, it is helpful to have a look in [Tor community Exit Guidelines](https://community.torproject.org/relay/community-resources/tor-exit-guidelines/). This chapter is an exert of tor page.
Note that Tor states:
> This FAQ is for informational purposes only and does not constitute legal advice.
*Check legal advice prior to running an exit relay*
* Understand the risks associated with running an exit relay; E.g., know legal paragraphs relevant in the country of operations:
- US [DMCA 512](https://www.law.cornell.edu/uscode/text/17/512); see [EFF's Legal FAQ for TOr Operators](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq) (a very good and relevant read for other countries as well)
- Germanys [TeleMedienGesetz 8](http://www.gesetze-im-internet.de/tmg/__8.html) and [15](http://www.gesetze-im-internet.de/tmg/__15.html)
- Netherlands: [Artikel 6:196c BW](http://wetten.overheid.nl/BWBR0005289/Boek6/Titel3/Afdeling4A/Artikel196c/)
- Austria: [E-Commerce-Gesetz 13](http://www.ris.bka.gv.at/Dokument.wxe?Abfrage=Bundesnormen&Dokumentnummer=NOR40025809)
- Sweden: [16-19 2002:562](https://lagen.nu/2002:562#P16S1)
* Top 3 advice
- Have an abuse response letter
- Run relay from a location that is not home
- Read through the legal resources that Tor-supportive lawyers put together: https://www.eff.org/pages/legal-faq-tor-relay-operators or https://www.noisebridge.net/wiki/Noisebridge_Tor/FBI
* Consult a lawyer / local digital rights association / the EFF prior to operating an exit relay, especially in a place where exit relay operators have been harassed or not operating before. Note that Tor DOES NOT provide legal advice for specific countries. It only provides general advice (itself or in partnership), eventually skewed towards [US audiences](https://www.eff.org/pages/legal-faq-tor-relay-operators).
*Run an exit relay within an entity*
As an organisation - it might help from a liability perspective
* Within your university
* With a node operators association (e.g., a Torservers.net partner)
* Within a company
*Be ready to respond to abuse complaints*
* Make your contact details (email, phone, or even fax) available, instead of those of the ISP
* Reply in a timely manner (e.g., 24 hours) using the [provided templates](https://community.torproject.org/relay/community-resources/tor-abuse-templates)
* Note that Tor states: *“We are not aware of any case that made it near a court, and we will do everything in our power to support you if it does.”*
* Document experience with ISPs at [community.torproject.org/relay/community-resources/good-bad-isps](https://community.torproject.org/relay/community-resources/good-bad-isps/)
Useful links:
* Tor abuse templates:
- [community.torproject.org/relay/community-resources/tor-abuse-templates/](https://community.torproject.org/relay/community-resources/tor-abuse-templates/)
- [gitlab.torproject.org/legacy/trac/-/wikis/doc/TorAbuseTemplates](https://gitlab.torproject.org/legacy/trac/-/wikis/doc/TorAbuseTemplates) (from 2020)
- [github.com/coldhakca/abuse-templates/blob/master/generic.template](https://github.com/coldhakca/abuse-templates/blob/master/generic.template)
* DMCA response templates:
- [community.torproject.org/relay/community-resources/eff-tor-legal-faq/tor-dmca-response/](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq/tor-dmca-response/)
- [2019.www.torproject.org/eff/tor-dmca-response.html](https://2019.www.torproject.org/eff/tor-dmca-response.html) (from 2011)
- [github.com/coldhakca/abuse-templates/blob/master/dmca.template](https://github.com/coldhakca/abuse-templates/blob/master/dmca.template)
## Legal environment - Findings from our legal team
```admonish warning
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://discord.com/invite/nym), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
```
The Swiss legal counsel and US legal counsel have so far provided the following advice:
### Switzerland
TBD soon.
### United States
A US counsel shared the following advice:
The legal risk faced by VPN operators subject to United States jurisdiction depends on various statutes and regulations related to privacy, anonymity, and electronic communications. The key areas to consider are: intermediary liability and exceptions, data protection, copyright infringement, export controls, criminal law, government requests for data and assistance, and third party liability.
As outlined in Part A, the United States treats VPNs as telecommunications networks subject to intermediary liability protection from wrongful conduct that occurs on its network. However, such protections do have exceptions including criminal law and copyright claims that are worth considering. In the United States, I am not aware of an individual ever being prosecuted or convicted for running a node for a dVPN or a Privacy Enhancing Network.
However, as discussed in Part B-C, VPN operators are subject to law enforcement requests for access or assistance in obtaining access to data relevant to an investigation into allegedly unlawful conduct that was facilitated by the network as an intermediary. As shown in Part C, governments may also request assistance from node operators for certain high-level and national security targets.
Finally, as outlined in Parts D-G, VPN operators may also be subject to non-criminal liability including (Part D) failing to respond to notices under the DMCA, (Part E) privacy and data protection law, (Part F) third party lawsuits stemming from wrongful acts committed using the network, and (G) export control violations.
## How to add legal information
Our aim is to establish a strong community network, sharing legal findings with each other. We would like to encourage all the current and future operators to do research about the situation in the jurisdiction they operate and update this page.
First of all, please join our [Node Operators Legal Forum](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) (Matrix chat) and post any information or questions there.
To add your information to this book, you can create a pull request directly to our [repository](https://github.com/nymtech/nym/tree/develop/documentation/operators/src), than ping the admins in the [Legal Forum chat](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) and we will review it as fast as possible.
To do so, follow the steps below:
1. Write your legal findings down in a text editor (Soon we will share a template)
2. Clone `nymtech/nym` repository and switch to develop branch
```sh
# Clone the repository
git clone https://github.com/nymtech/nym
# Go to the directory nym
cd nym
# Switch to branch develop
git checkout develop
# Update the repository
git pull origin develop
```
3. Make your own branch based off `develop` and switch to it
```sh
git branch operators/legal-forum/<MY_BRANCH_NAME> # choose a descriptive and consiose name without using <>
git checkout operators/legal-forum/<MY_BRANCH_NAME>
# you can double check that you are on the right branch
git branch
```
4. Save your legal findings as `<FILE_NAME>.md` to `/nym/documentation/operators/src/legal`
5. Don't change anything in `SUMMARY.md`, the admins will do it when merging
6. Add, commit and push your changes
```sh
cd documentation/operators/src/legal
git add <FILE_NAME>.md
git commit -am "<describe your changes>"
git push origin operators/legal-forum/<MY_BRANCH_NAME>
```
7. Notify others in the [Node Operators Legal Forum](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) (Matrix chat)
+1 -1
View File
@@ -101,7 +101,7 @@ impl ApiCmdProcessor {
}
fn ephemera_config<A: Application>(
ephemera: &mut Ephemera<A>,
ephemera: &Ephemera<A>,
reply: Sender<api::Result<ApiEphemeraConfig>>,
) {
let node_info = ephemera.node_info.clone();
+3 -3
View File
@@ -191,10 +191,10 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
let block_manager = self.init_block_manager(&mut storage)?;
let (mut shutdown_manager, shutdown_handle) = ShutdownManager::init();
let (shutdown_manager, shutdown_handle) = ShutdownManager::init();
let mut service_data = ServiceInfo::default();
let services = self.init_services(&mut service_data, &mut shutdown_manager, provider)?;
let services = self.init_services(&mut service_data, &shutdown_manager, provider)?;
Ok(EphemeraStarterWithProvider {
with_application: self,
@@ -237,7 +237,7 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
>(
&mut self,
service_data: &mut ServiceInfo,
shutdown_manager: &mut ShutdownManager,
shutdown_manager: &ShutdownManager,
provider: P,
) -> anyhow::Result<Vec<BoxFuture<'static, anyhow::Result<()>>>> {
let services = vec![
+4 -4
View File
@@ -1,5 +1,5 @@
import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api';
import { toPercentIntegerString } from '../utils';
import { toPercentInteger } from '../utils';
export type GatewayRowType = {
id: string;
@@ -9,7 +9,7 @@ export type GatewayRowType = {
host: string;
location: string;
version: string;
node_performance: string;
node_performance: number;
};
export type GatewayEnrichedRowType = GatewayRowType & {
@@ -30,7 +30,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
bond: gw.pledge_amount.amount || 0,
host: gw.gateway.host || '',
version: gw.gateway.version || '',
node_performance: toPercentIntegerString(gw.node_performance.last_24h),
node_performance: toPercentInteger(gw.node_performance.last_24h),
}));
}
@@ -47,6 +47,6 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe
mixPort: gateway.gateway.mix_port || 0,
routingScore: `${report.most_recent}%`,
avgUptime: `${report.last_day || report.last_hour}%`,
node_performance: toPercentIntegerString(gateway.node_performance.most_recent),
node_performance: toPercentInteger(gateway.node_performance.most_recent),
};
}
+9 -9
View File
@@ -1,6 +1,6 @@
/* eslint-disable camelcase */
import { MixNodeResponse, MixNodeResponseItem, MixnodeStatus, NodePerformance } from '../../typeDefs/explorer-api';
import { toPercentIntegerString } from '../../utils';
import { MixNodeResponse, MixNodeResponseItem, MixnodeStatus } from '../../typeDefs/explorer-api';
import { toPercentInteger, toPercentIntegerString } from '../../utils';
import { unymToNym } from '../../utils/currency';
export type MixnodeRowType = {
@@ -15,11 +15,11 @@ export type MixnodeRowType = {
pledge_amount: number;
host: string;
layer: string;
profit_percentage: string;
profit_percentage: number;
avg_uptime: string;
stake_saturation: React.ReactNode;
operating_cost: string;
node_performance: NodePerformance['most_recent'];
operating_cost: number;
node_performance: number;
blacklisted: boolean;
};
@@ -32,7 +32,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
const delegations = Number(item.total_delegation.amount) || 0;
const totalBond = pledge + delegations;
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
const profitPercentage = toPercentIntegerString(item.profit_margin_percent) || 0;
const profitPercentage = toPercentInteger(item.profit_margin_percent) || 0;
const uncappedSaturation = typeof item.uncapped_saturation === 'number' ? item.uncapped_saturation * 100 : 0;
return {
@@ -47,11 +47,11 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
pledge_amount: pledge,
host: item?.mix_node?.host || '',
layer: item?.layer || '',
profit_percentage: `${profitPercentage}%`,
profit_percentage: profitPercentage,
avg_uptime: `${toPercentIntegerString(item.node_performance.last_24h)}%`,
stake_saturation: Number(uncappedSaturation.toFixed(2)),
operating_cost: `${unymToNym(item.operating_cost?.amount, 6)} NYM`,
node_performance: `${toPercentIntegerString(item.node_performance.most_recent)}%`,
operating_cost: Number(unymToNym(item.operating_cost?.amount, 6)) || 0,
node_performance: toPercentInteger(item.node_performance.most_recent),
blacklisted: item.blacklisted,
};
}
+3 -3
View File
@@ -210,7 +210,7 @@ export const PageMixnodes: FCWithChildren = () => {
component={RRDLink}
to={`/network-components/mixnode/${params.row.mix_id}`}
>
{params.value}
{params.value}%
</MuiLink>
),
},
@@ -233,7 +233,7 @@ export const PageMixnodes: FCWithChildren = () => {
component={RRDLink}
to={`/network-components/mixnode/${params.row.mix_id}`}
>
{params.value}
{params.value} NYM
</MuiLink>
),
},
@@ -256,7 +256,7 @@ export const PageMixnodes: FCWithChildren = () => {
component={RRDLink}
to={`/network-components/mixnode/${params.row.mix_id}`}
>
{params.value}
{params.value}%
</MuiLink>
),
},
+1
View File
@@ -55,6 +55,7 @@ export const splice = (start: number, deleteCount: number, address?: string): st
* @returns A stringified integer
*/
export const toPercentIntegerString = (value: string) => Math.round(Number(value) * 100).toString();
export const toPercentInteger = (value: string) => Math.round(Number(value) * 100);
export const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
const progressBarValue = value?.progressBarValue || 0;
@@ -1,5 +1,4 @@
use std::{
collections::HashMap,
fmt,
hash::{Hash, Hasher},
net::SocketAddr,
@@ -8,6 +7,7 @@ use std::{
};
use base64::{engine::general_purpose, Engine};
use dashmap::DashMap;
use hmac::{Hmac, Mac};
use nym_crypto::asymmetric::encryption::PrivateKey;
use serde::{Deserialize, Serialize};
@@ -178,4 +178,4 @@ impl<'de> Deserialize<'de> for ClientPublicKey {
}
}
pub(crate) type ClientRegistry = HashMap<SocketAddr, Client>;
pub(crate) type ClientRegistry = DashMap<SocketAddr, Client>;
+15 -16
View File
@@ -14,8 +14,7 @@ use crate::node::http::ApiState;
async fn process_final_message(client: Client, state: Arc<ApiState>) -> StatusCode {
let preshared_nonce = {
let in_progress_ro = state.registration_in_progress.read().await;
if let Some(nonce) = in_progress_ro.get(client.pub_key()) {
if let Some(nonce) = state.registration_in_progress.get(client.pub_key()) {
*nonce
} else {
return StatusCode::BAD_REQUEST;
@@ -27,12 +26,10 @@ async fn process_final_message(client: Client, state: Arc<ApiState>) -> StatusCo
.is_ok()
{
{
let mut in_progress_rw = state.registration_in_progress.write().await;
in_progress_rw.remove(client.pub_key());
state.registration_in_progress.remove(client.pub_key());
}
{
let mut registry_rw = state.client_registry.write().await;
registry_rw.insert(client.socket(), client);
state.client_registry.insert(client.socket(), client);
}
return StatusCode::OK;
}
@@ -42,8 +39,9 @@ async fn process_final_message(client: Client, state: Arc<ApiState>) -> StatusCo
async fn process_init_message(init_message: InitMessage, state: Arc<ApiState>) -> u64 {
let nonce: u64 = fastrand::u64(..);
let mut registry_rw = state.registration_in_progress.write().await;
registry_rw.insert(init_message.pub_key().clone(), nonce);
state
.registration_in_progress
.insert(init_message.pub_key().clone(), nonce);
nonce
}
@@ -67,12 +65,12 @@ pub(crate) async fn register_client(
pub(crate) async fn get_all_clients(
State(state): State<Arc<ApiState>>,
) -> (StatusCode, Json<Vec<ClientPublicKey>>) {
let registry_ro = state.client_registry.read().await;
(
StatusCode::OK,
Json(
registry_ro
.values()
state
.client_registry
.iter()
.map(|c| c.pub_key().clone())
.collect::<Vec<ClientPublicKey>>(),
),
@@ -87,12 +85,13 @@ pub(crate) async fn get_client(
Ok(pub_key) => pub_key,
Err(_) => return (StatusCode::BAD_REQUEST, Json(vec![])),
};
let registry_ro = state.client_registry.read().await;
let clients = registry_ro
let clients = state
.client_registry
.iter()
.filter_map(|(_, c)| {
if c.pub_key() == &pub_key {
Some(c.clone())
.filter_map(|r| {
let client = r.value();
if client.pub_key() == &pub_key {
Some(client.clone())
} else {
None
}
+15 -16
View File
@@ -1,12 +1,12 @@
use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;
use axum::{
routing::{get, post},
Router,
};
use dashmap::DashMap;
use log::info;
use nym_crypto::asymmetric::encryption;
use tokio::sync::RwLock;
mod api;
use api::v1::client_registry::*;
@@ -16,9 +16,9 @@ use super::client_handling::client_registration::{ClientPublicKey, ClientRegistr
const ROUTE_PREFIX: &str = "/api/v1/gateway/client-interfaces/wireguard";
pub struct ApiState {
client_registry: Arc<RwLock<ClientRegistry>>,
client_registry: Arc<ClientRegistry>,
sphinx_key_pair: Arc<encryption::KeyPair>,
registration_in_progress: Arc<RwLock<HashMap<ClientPublicKey, u64>>>,
registration_in_progress: Arc<DashMap<ClientPublicKey, u64>>,
}
fn router_with_state(state: Arc<ApiState>) -> Router {
@@ -33,7 +33,7 @@ fn router_with_state(state: Arc<ApiState>) -> Router {
}
pub(crate) async fn start_http_api(
client_registry: Arc<RwLock<ClientRegistry>>,
client_registry: Arc<ClientRegistry>,
sphinx_key_pair: Arc<encryption::KeyPair>,
) {
// Port should be 80 post smoosh
@@ -46,7 +46,7 @@ pub(crate) async fn start_http_api(
let state = Arc::new(ApiState {
client_registry,
sphinx_key_pair,
registration_in_progress: Arc::new(RwLock::new(HashMap::new())),
registration_in_progress: Arc::new(DashMap::new()),
});
let routes = router_with_state(state);
@@ -62,17 +62,17 @@ pub(crate) async fn start_http_api(
mod test {
use std::net::SocketAddr;
use std::str::FromStr;
use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;
use axum::body::Body;
use axum::http::Request;
use axum::http::StatusCode;
use dashmap::DashMap;
use hmac::Mac;
use tower::Service;
use tower::ServiceExt;
use nym_crypto::asymmetric::encryption;
use tokio::sync::RwLock;
use x25519_dalek::{PublicKey, StaticSecret};
use crate::node::client_handling::client_registration::{
@@ -105,8 +105,8 @@ mod test {
let client_dh = client_static_private.diffie_hellman(&gateway_static_public);
let registration_in_progress = Arc::new(RwLock::new(HashMap::new()));
let client_registry = Arc::new(RwLock::new(HashMap::new()));
let registration_in_progress = Arc::new(DashMap::new());
let client_registry = Arc::new(DashMap::new());
let state = Arc::new(ApiState {
client_registry: Arc::clone(&client_registry),
@@ -136,7 +136,7 @@ mod test {
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(!registration_in_progress.read().await.is_empty());
assert!(!registration_in_progress.is_empty());
let nonce: Option<u64> =
serde_json::from_slice(&hyper::body::to_bytes(response.into_body()).await.unwrap())
@@ -171,7 +171,7 @@ mod test {
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(!client_registry.read().await.is_empty());
assert!(!client_registry.is_empty());
let clients_request = Request::builder()
.method("GET")
@@ -194,11 +194,10 @@ mod test {
assert!(!clients.is_empty());
let ro_clients = client_registry.read().await.clone();
assert_eq!(
ro_clients
.values()
.map(|c| c.pub_key().clone())
client_registry
.iter()
.map(|c| c.value().pub_key().clone())
.collect::<Vec<ClientPublicKey>>(),
clients
)
+17 -6
View File
@@ -18,6 +18,7 @@ use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandle
use crate::node::statistics::collector::GatewayStatisticsCollector;
use crate::node::storage::Storage;
use anyhow::bail;
use dashmap::DashMap;
use futures::channel::{mpsc, oneshot};
use log::*;
use nym_crypto::asymmetric::{encryption, identity};
@@ -29,12 +30,10 @@ use nym_task::{TaskClient, TaskManager};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::collections::HashMap;
use std::error::Error;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
pub(crate) mod client_handling;
pub(crate) mod helpers;
@@ -91,7 +90,7 @@ pub(crate) struct Gateway<St = PersistentStorage> {
sphinx_keypair: Arc<encryption::KeyPair>,
storage: St,
client_registry: Arc<RwLock<ClientRegistry>>,
client_registry: Arc<ClientRegistry>,
}
impl<St> Gateway<St> {
@@ -107,7 +106,7 @@ impl<St> Gateway<St> {
sphinx_keypair: Arc::new(helpers::load_sphinx_keys(&config)?),
config,
network_requester_opts,
client_registry: Arc::new(RwLock::new(HashMap::new())),
client_registry: Arc::new(DashMap::new()),
})
}
@@ -125,7 +124,7 @@ impl<St> Gateway<St> {
identity_keypair: Arc::new(identity_keypair),
sphinx_keypair: Arc::new(sphinx_keypair),
storage,
client_registry: Arc::new(RwLock::new(HashMap::new())),
client_registry: Arc::new(DashMap::new()),
}
}
@@ -157,6 +156,15 @@ impl<St> Gateway<St> {
mixnet_handling::Listener::new(listening_address, shutdown).start(connection_handler);
}
#[cfg(feature = "wireguard")]
async fn start_wireguard(
&self,
shutdown: TaskClient,
) -> Result<(), Box<dyn Error + Send + Sync>> {
// TODO: possibly we should start the UDP listener and TUN device explicitly here
nym_wireguard::start_wireguard(shutdown).await
}
fn start_client_websocket_listener(
&self,
forwarding_channel: MixForwardingSender,
@@ -379,7 +387,10 @@ impl<St> Gateway<St> {
// Once this is a bit more mature, make this a commandline flag instead of a compile time
// flag
#[cfg(feature = "wireguard")]
if let Err(err) = nym_wireguard::start_wireguard(shutdown.subscribe()).await {
if let Err(err) = self
.start_wireguard(shutdown.subscribe().named("wireguard"))
.await
{
// that's a nasty workaround, but anyhow errors are generally nicer, especially on exit
bail!("{err}")
}
+263 -130
View File
@@ -52,17 +52,89 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.18.6"
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/code-frame/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/code-frame/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/compat-data": {
"version": "7.18.8",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz",
@@ -112,13 +184,14 @@
}
},
"node_modules/@babel/generator": {
"version": "7.18.12",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz",
"integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"dev": true,
"dependencies": {
"@babel/types": "^7.18.10",
"@babel/types": "^7.23.0",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
@@ -167,34 +240,34 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz",
"integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dev": true,
"dependencies": {
"@babel/template": "^7.18.6",
"@babel/types": "^7.18.9"
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dev": true,
"dependencies": {
"@babel/types": "^7.18.6"
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
@@ -253,30 +326,30 @@
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dev": true,
"dependencies": {
"@babel/types": "^7.18.6"
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz",
"integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -306,13 +379,13 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
},
"engines": {
@@ -391,9 +464,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.18.11",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz",
"integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -565,33 +638,33 @@
}
},
"node_modules/@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.18.10",
"@babel/types": "^7.18.10"
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.18.11",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz",
"integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.10",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.18.9",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.18.11",
"@babel/types": "^7.18.10",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -609,13 +682,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.18.10",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -1099,13 +1172,13 @@
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@nodelib/fs.scandir": {
@@ -4880,12 +4953,71 @@
}
},
"@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"requires": {
"@babel/highlight": "^7.18.6"
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"@babel/compat-data": {
@@ -4926,13 +5058,14 @@
}
},
"@babel/generator": {
"version": "7.18.12",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz",
"integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"dev": true,
"requires": {
"@babel/types": "^7.18.10",
"@babel/types": "^7.23.0",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"dependencies": {
@@ -4970,28 +5103,28 @@
}
},
"@babel/helper-environment-visitor": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"dev": true
},
"@babel/helper-function-name": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz",
"integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dev": true,
"requires": {
"@babel/template": "^7.18.6",
"@babel/types": "^7.18.9"
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
}
},
"@babel/helper-hoist-variables": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dev": true,
"requires": {
"@babel/types": "^7.18.6"
"@babel/types": "^7.22.5"
}
},
"@babel/helper-module-imports": {
@@ -5035,24 +5168,24 @@
}
},
"@babel/helper-split-export-declaration": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dev": true,
"requires": {
"@babel/types": "^7.18.6"
"@babel/types": "^7.22.5"
}
},
"@babel/helper-string-parser": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz",
"integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"dev": true
},
"@babel/helper-validator-option": {
@@ -5073,13 +5206,13 @@
}
},
"@babel/highlight": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
},
"dependencies": {
@@ -5142,9 +5275,9 @@
}
},
"@babel/parser": {
"version": "7.18.11",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz",
"integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
"dev": true
},
"@babel/plugin-syntax-async-generators": {
@@ -5265,30 +5398,30 @@
}
},
"@babel/template": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.18.10",
"@babel/types": "^7.18.10"
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
}
},
"@babel/traverse": {
"version": "7.18.11",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz",
"integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.10",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.18.9",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.18.11",
"@babel/types": "^7.18.10",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -5302,13 +5435,13 @@
}
},
"@babel/types": {
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.18.10",
"@babel/helper-validator-identifier": "^7.18.6",
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
}
},
@@ -5687,13 +5820,13 @@
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"@nodelib/fs.scandir": {
+116 -26
View File
@@ -17,6 +17,14 @@
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
"@babel/highlight" "^7.22.13"
chalk "^2.4.2"
"@babel/compat-data@^7.18.8":
version "7.18.8"
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz"
@@ -52,6 +60,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-compilation-targets@^7.18.9":
version "7.18.9"
resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz"
@@ -67,20 +85,25 @@
resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
"@babel/helper-function-name@^7.18.9":
version "7.18.9"
resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz"
integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==
dependencies:
"@babel/template" "^7.18.6"
"@babel/types" "^7.18.9"
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz"
integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==
"@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/types" "^7.18.6"
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-module-imports@^7.18.6":
version "7.18.6"
@@ -122,16 +145,33 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.18.10":
version "7.18.10"
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz"
integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.18.6":
version "7.18.6"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz"
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
"@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz"
@@ -155,11 +195,25 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11":
"@babel/highlight@^7.22.13":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10":
version "7.18.11"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz"
integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz"
@@ -260,19 +314,28 @@
"@babel/parser" "^7.18.10"
"@babel/types" "^7.18.10"
"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2":
version "7.18.11"
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz"
integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.10"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.18.9"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.18.11"
"@babel/types" "^7.18.10"
"@babel/code-frame" "^7.22.13"
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
@@ -285,6 +348,15 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
@@ -555,6 +627,11 @@
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz"
@@ -565,6 +642,11 @@
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz"
@@ -573,6 +655,14 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping@^0.3.17":
version "0.3.19"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@@ -1028,7 +1118,7 @@ caniuse-lite@^1.0.30001370:
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz"
integrity sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==
chalk@^2.0.0:
chalk@^2.0.0, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "extension-storage"
version = "1.2.0-rc.10"
version = "1.2.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"
+6
View File
@@ -2,6 +2,12 @@
## [Unreleased]
## [v1.2.9] (2023-10-10)
- Wallet: Introduce edit account name ([#3895])
[#3895]: https://github.com/nymtech/nym/pull/3895
## [v1.2.8] (2023-08-23)
- [hotfix]: don't assign invalid fields when crossing the JS boundary ([#3805])
+1 -1
View File
@@ -3512,7 +3512,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.2.8"
version = "1.2.9"
dependencies = [
"async-trait",
"base64 0.13.1",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-wallet-app",
"version": "1.2.8",
"version": "1.2.9",
"main": "index.js",
"license": "MIT",
"scripts": {
@@ -31,7 +31,7 @@
"@nymproject/mui-theme": "^1.0.0",
"@nymproject/react": "^1.0.0",
"@nymproject/types": "^1.0.0",
"@nymproject/node-tester": ">=1.2.0-rc.8",
"@nymproject/node-tester": "^1.0.0",
"@storybook/react": "^6.5.15",
"@tauri-apps/api": "^1.2.0",
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
@@ -123,4 +123,4 @@
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym_wallet"
version = "1.2.8"
version = "1.2.9"
description = "Nym Native Wallet"
authors = ["Nym Technologies SA"]
license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "1.2.8"
"version": "1.2.9"
},
"build": {
"distDir": "../dist",
@@ -29,7 +29,7 @@ export const items: DelegationWithEverything[] = [
accumulated_by_operator: { amount: '100', denom: 'nym' },
owner: '',
block_height: BigInt(100),
stake_saturation: '0.5',
stake_saturation: '0.25',
avg_uptime_percent: 0.5,
uses_vesting_contract_tokens: false,
pending_events: [],
@@ -38,7 +38,7 @@ export const items: DelegationWithEverything[] = [
{
mix_id: 2,
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
amount: { amount: '100', denom: 'nym' },
amount: { amount: '1010', denom: 'nym' },
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
cost_params: {
@@ -49,11 +49,11 @@ export const items: DelegationWithEverything[] = [
},
},
accumulated_by_delegates: { amount: '50', denom: 'nym' },
accumulated_by_operator: { amount: '100', denom: 'nym' },
accumulated_by_operator: { amount: '200', denom: 'nym' },
owner: '',
block_height: BigInt(4000),
stake_saturation: '0.5',
avg_uptime_percent: 0.1,
stake_saturation: '0.43',
avg_uptime_percent: 0.22,
uses_vesting_contract_tokens: true,
pending_events: [],
mixnode_is_unbonding: true,
@@ -61,18 +61,18 @@ export const items: DelegationWithEverything[] = [
{
mix_id: 3,
node_identity: '',
amount: { amount: '100', denom: 'nym' },
amount: { amount: '300', denom: 'nym' },
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
cost_params: {
profit_margin_percent: '0.1122323949234',
interval_operating_cost: {
amount: '40',
amount: '50',
denom: 'nym',
},
},
accumulated_by_delegates: { amount: '50', denom: 'nym' },
accumulated_by_operator: { amount: '100', denom: 'nym' },
accumulated_by_operator: { amount: '300', denom: 'nym' },
owner: '',
block_height: BigInt(4000),
stake_saturation: '0.5',
@@ -84,18 +84,18 @@ export const items: DelegationWithEverything[] = [
{
mix_id: 4,
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
amount: { amount: '100', denom: 'nym' },
amount: { amount: '201', denom: 'nym' },
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
cost_params: {
profit_margin_percent: '0.1122323949234',
interval_operating_cost: {
amount: '40',
amount: '60',
denom: 'nym',
},
},
accumulated_by_delegates: { amount: '50', denom: 'nym' },
accumulated_by_operator: { amount: '100', denom: 'nym' },
accumulated_by_operator: { amount: '202', denom: 'nym' },
owner: '',
block_height: BigInt(4000),
stake_saturation: '0.5',
@@ -113,7 +113,7 @@ export const items: DelegationWithEverything[] = [
cost_params: {
profit_margin_percent: '0.1122323949234',
interval_operating_cost: {
amount: '40',
amount: '80',
denom: 'nym',
},
},
@@ -130,11 +130,11 @@ export const items: DelegationWithEverything[] = [
{
mix_id: 6,
node_identity: '',
amount: { amount: '100', denom: 'nym' },
amount: { amount: '202', denom: 'nym' },
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
cost_params: {
profit_margin_percent: '0.1122323949234',
profit_margin_percent: '0.8',
interval_operating_cost: {
amount: '40',
denom: 'nym',
@@ -155,9 +155,9 @@ export const items: DelegationWithEverything[] = [
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
amount: { amount: '10', denom: 'nym' },
amount: { amount: '202', denom: 'nym' },
cost_params: {
profit_margin_percent: '0.1122323949234',
profit_margin_percent: '0.59',
interval_operating_cost: {
amount: '40',
denom: 'nym',
@@ -190,7 +190,7 @@ export const items: DelegationWithEverything[] = [
accumulated_by_operator: { amount: '100', denom: 'nym' },
owner: '',
block_height: BigInt(4000),
stake_saturation: '0.5',
stake_saturation: '0.9',
avg_uptime_percent: 0.1,
uses_vesting_contract_tokens: true,
pending_events: [],
@@ -199,11 +199,11 @@ export const items: DelegationWithEverything[] = [
{
mix_id: 9,
node_identity: '',
amount: { amount: '100', denom: 'nym' },
amount: { amount: '1000', denom: 'nym' },
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
cost_params: {
profit_margin_percent: '0.1122323949234',
profit_margin_percent: '0.4',
interval_operating_cost: {
amount: '40',
denom: 'nym',
@@ -213,7 +213,7 @@ export const items: DelegationWithEverything[] = [
accumulated_by_operator: { amount: '100', denom: 'nym' },
owner: '',
block_height: BigInt(4000),
stake_saturation: '0.5',
stake_saturation: '0.9',
avg_uptime_percent: 0.1,
uses_vesting_contract_tokens: true,
pending_events: [],
@@ -259,8 +259,8 @@ export const items: DelegationWithEverything[] = [
accumulated_by_operator: { amount: '100', denom: 'nym' },
owner: '',
block_height: BigInt(4000),
stake_saturation: '0.5',
avg_uptime_percent: 0.1,
stake_saturation: '0.56',
avg_uptime_percent: 0.9,
uses_vesting_contract_tokens: true,
pending_events: [],
mixnode_is_unbonding: true,
@@ -2,17 +2,17 @@ import React from 'react';
import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TableSortLabel } from '@mui/material';
import { visuallyHidden } from '@mui/utils';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import { orderBy as _orderBy } from 'lodash';
import { DelegationWithEverything } from '@nymproject/types';
import { useSortDelegations } from 'src/hooks/useSortDelegations';
import { DelegationListItemActions } from './DelegationActions';
import { isDelegation, isPendingDelegation, TDelegations } from '../../context/delegations';
import { DelegationItem } from './DelegationItem';
import { PendingDelegationItem } from './PendingDelegationItem';
import { LoadingModal } from '../Modals/LoadingModal';
import { isDelegation, isPendingDelegation, TDelegations } from '../../context/delegations';
type Order = 'asc' | 'desc';
export type Order = 'asc' | 'desc';
type AdditionalTypes = { profit_margin_percent: number; operating_cost: number };
type SortingKeys = keyof AdditionalTypes | keyof DelegationWithEverything;
export type SortingKeys = keyof AdditionalTypes | keyof DelegationWithEverything;
interface EnhancedTableProps {
onRequestSort: (event: React.MouseEvent<unknown>, property: string) => void;
@@ -83,10 +83,6 @@ const EnhancedTableHead: FCWithChildren<EnhancedTableProps> = ({ order, orderBy,
};
// Pin delegations on unbonded nodes to the top of the list
const sortByUnbondedMixnodeFirst = (a: any) => {
if (!a.node_identity) return -1;
return 1;
};
export const DelegationList: FCWithChildren<{
isLoading?: boolean;
@@ -98,37 +94,13 @@ export const DelegationList: FCWithChildren<{
const [order, setOrder] = React.useState<Order>('asc');
const [orderBy, setOrderBy] = React.useState<SortingKeys>('delegated_on_iso_datetime');
const handleRequestSort = (event: React.MouseEvent<unknown>, property: any) => {
const handleRequestSort = (_: React.MouseEvent<unknown>, property: any) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
// if sorting by either amount or unclaimed_rewards
// base sorting on their number counterparts
const mapOrderBy = (key: SortingKeys) => {
if (key === 'amount') return 'delegationValue';
if (key === 'unclaimed_rewards') return 'operatorReward';
if (key === 'profit_margin_percent') return 'profitMarginValue';
if (key === 'operating_cost') return 'operatorCostValue';
return key;
};
const mapAndSort = (_items: TDelegations) => {
const map = _items.map((item) =>
isDelegation(item)
? {
...item,
delegationValue: Number(item.amount.amount),
operatorReward: Number(item.unclaimed_rewards?.amount),
profitMarginValue: Number(item.cost_params?.profit_margin_percent),
operatorCostValue: Number(item.cost_params?.interval_operating_cost),
}
: item,
);
return _orderBy(map, mapOrderBy(orderBy), order).sort(sortByUnbondedMixnodeFirst);
};
const sorted = useSortDelegations(items, order, orderBy);
return (
<TableContainer>
@@ -136,8 +108,8 @@ export const DelegationList: FCWithChildren<{
<Table sx={{ width: '100%' }}>
<EnhancedTableHead order={order} orderBy={orderBy} onRequestSort={handleRequestSort} />
<TableBody>
{items?.length
? mapAndSort(items).map((item: any) => {
{sorted?.length
? sorted.map((item: any) => {
if (isPendingDelegation(item)) return <PendingDelegationItem item={item} explorerUrl={explorerUrl} />;
if (isDelegation(item))
return (
+1 -8
View File
@@ -125,7 +125,6 @@ export type TBondingContext = {
updateBondAmount: (data: TUpdateBondArgs, tokenPool: TokenPool) => Promise<TransactionExecuteResult | undefined>;
redeemRewards: (fee?: FeeDetails) => Promise<TransactionExecuteResult | undefined>;
updateMixnode: (pm: string, fee?: FeeDetails) => Promise<TransactionExecuteResult | undefined>;
checkOwnership: () => Promise<void>;
generateMixnodeMsgPayload: (data: TBondMixnodeSignatureArgs) => Promise<string | undefined>;
generateGatewayMsgPayload: (data: TBondGatewaySignatureArgs) => Promise<string | undefined>;
isVestingAccount: boolean;
@@ -152,9 +151,6 @@ export const BondingContext = createContext<TBondingContext>({
updateMixnode: async () => {
throw new Error('Not implemented');
},
checkOwnership(): Promise<void> {
throw new Error('Not implemented');
},
generateMixnodeMsgPayload: async () => {
throw new Error('Not implemented');
},
@@ -171,7 +167,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
const [isVestingAccount, setIsVestingAccount] = useState(false);
const { userBalance, clientDetails } = useContext(AppContext);
const { ownership, isLoading: isOwnershipLoading, checkOwnership } = useCheckOwnership();
const { ownership, isLoading: isOwnershipLoading } = useCheckOwnership();
useEffect(() => {
userBalance.fetchBalance();
@@ -342,8 +338,6 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
setIsLoading(true);
setError(undefined);
await checkOwnership();
if (ownership.hasOwnership && ownership.nodeType === EnumNodeType.mixnode && clientDetails) {
try {
const data = await getMixnodeBondDetails();
@@ -617,7 +611,6 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
refresh,
redeemRewards,
updateBondAmount,
checkOwnership,
generateMixnodeMsgPayload,
generateGatewayMsgPayload,
isVestingAccount,
+3 -3
View File
@@ -26,11 +26,11 @@ let mockDelegations: DelegationWithEverything[] = [
cost_params: {
profit_margin_percent: '0.04',
interval_operating_cost: {
amount: '40',
amount: '20',
denom: 'nym',
},
},
stake_saturation: '0.5',
stake_saturation: '0.2',
avg_uptime_percent: 0.5,
accumulated_by_delegates: { amount: '0', denom: 'nym' },
accumulated_by_operator: { amount: '0', denom: 'nym' },
@@ -51,7 +51,7 @@ let mockDelegations: DelegationWithEverything[] = [
cost_params: {
profit_margin_percent: '0.04',
interval_operating_cost: {
amount: '40',
amount: '60',
denom: 'nym',
},
},
+2 -3
View File
@@ -4,7 +4,7 @@ import { AppContext } from '../context/main';
import { checkGatewayOwnership, checkMixnodeOwnership, getVestingPledgeInfo } from '../requests';
import { EnumNodeType, TNodeOwnership } from '../types';
const initial = {
const initial: TNodeOwnership = {
hasOwnership: false,
nodeType: undefined,
vestingPledge: undefined,
@@ -18,8 +18,7 @@ export const useCheckOwnership = () => {
const [error, setError] = useState<string>();
const checkOwnership = useCallback(async () => {
const status = initial as TNodeOwnership;
const status = { ...initial };
try {
const [ownsMixnode, ownsGateway] = await Promise.all([checkMixnodeOwnership(), checkGatewayOwnership()]);
@@ -0,0 +1,45 @@
import { orderBy as _orderBy } from 'lodash';
import { Order, SortingKeys } from 'src/components/Delegation/DelegationList';
import { TDelegations, isDelegation } from 'src/context/delegations';
type MappedTypes = 'delegationValue' | 'operatorReward' | 'profitMarginValue' | 'operatorCostValue';
export const useSortDelegations = (delegationItems: TDelegations, order: Order, orderBy: SortingKeys) => {
const unbondedDelegations = delegationItems.filter((delegation) => !delegation.node_identity);
const delegations = delegationItems.filter((delegation) => delegation.node_identity);
// example of a mapped type in typescript
const mapOrderBy = (key: SortingKeys): MappedTypes | SortingKeys => {
switch (key) {
case 'amount':
return 'delegationValue';
case 'unclaimed_rewards':
return 'operatorReward';
case 'profit_margin_percent':
return 'profitMarginValue';
case 'operating_cost':
return 'operatorCostValue';
default:
return key;
}
};
const mapAndSort = (_items: TDelegations) => {
const mapToNumberType = _items.map((item) =>
isDelegation(item)
? {
...item,
delegationValue: Number(item.amount.amount),
operatorReward: Number(item.unclaimed_rewards?.amount),
profitMarginValue: Number(item.cost_params?.profit_margin_percent),
operatorCostValue: Number(item.cost_params?.interval_operating_cost.amount),
}
: item,
);
const ordered = _orderBy(mapToNumberType, mapOrderBy(orderBy), order).sort();
return ordered;
};
return [...unbondedDelegations, ...mapAndSort(delegations)];
};
+3 -12
View File
@@ -34,17 +34,8 @@ const Bonding = () => {
const navigate = useNavigate();
const {
bondedNode,
bondMixnode,
bondGateway,
redeemRewards,
isLoading,
checkOwnership,
updateBondAmount,
error,
refresh,
} = useBondingContext();
const { bondedNode, bondMixnode, bondGateway, redeemRewards, isLoading, updateBondAmount, error, refresh } =
useBondingContext();
useEffect(() => {
if (bondedNode && isMixnode(bondedNode) && bondedNode.uncappedStakeSaturation) {
@@ -54,7 +45,7 @@ const Bonding = () => {
const handleCloseModal = async () => {
setShowModal(undefined);
await checkOwnership();
refresh();
};
const handleError = (err: string) => {
+15 -10
View File
@@ -6,7 +6,10 @@
"workspaces": [
"dist/wasm/**",
"dist/node/**",
"sdk/typescript/packages/**",
"dist/ts/**",
"sdk/typescript/packages/mui-theme",
"sdk/typescript/packages/react-components",
"sdk/typescript/packages/validator-client",
"ts-packages/*",
"nym-wallet",
"nym-connect/**",
@@ -16,22 +19,22 @@
],
"scripts": {
"nuke": "npx rimraf **/node_modules node_modules",
"scrub": "npx rimraf **/dist dist",
"clean": "lerna run clean",
"build:ci:sdk": "run-s build:types build:packages build:wasm build:sdk:ci",
"build:sdk:ci": "lerna run --scope '{@nymproject/sdk,@nymproject/node-tester,@nymproject/sdk-react,@nymproject/mix-fetch}' build:dev --stream",
"build": "run-s build:types build:packages",
"build:wasm": "make sdk-wasm-build",
"build:sdk": "make sdk-typescript-build",
"build:types": "lerna run --scope @nymproject/types build --stream",
"build:packages": "run-s build:packages:theme build:packages:react",
"build:packages:theme": "lerna run --scope @nymproject/mui-theme build",
"build:packages:react": "lerna run --scope @nymproject/react build",
"build:react-example": "lerna run --scope @nymproject/react-webpack-with-theme-example build --stream",
"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",
"build:ci:storybook": "yarn build && yarn dev:on && run-p build:react-example build:playground && yarn build:ci:storybook:collect-artifacts",
"build:ci:storybook:collect-artifacts": "mkdir -p ts-packages/dist && mv sdk/typescript/packages/react-components/storybook-static ts-packages/dist/storybook && mv sdk/typescript/examples/react/mui-theme/dist ts-packages/dist/example",
"prebuild:ci": "yarn dev:on && yarn",
"build:ci": "run-s build:types build:packages build:wasm build:ci:sdk",
"postbuild:ci": "yarn dev:off",
"build:ci:sdk": "lerna run --scope '{@nymproject/sdk,@nymproject/node-tester,@nymproject/sdk-react,@nymproject/mix-fetch}' build:dev --stream",
"docs:prod:build": "run-s docs:prod:build:ws",
"docs:prod:build:ws": "lerna run docs:prod:build --stream",
"sdk:build": "./sdk/typescript/scripts/build-prod-sdk.sh",
@@ -40,7 +43,9 @@
"lint:fix": "lerna run lint:fix --stream",
"tsc": "lerna run tsc --stream",
"types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app",
"audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run"
"audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run",
"dev:on": "node sdk/typescript/scripts/dev-mode-add.mjs",
"dev:off": "node sdk/typescript/scripts/dev-mode-remove.mjs"
},
"devDependencies": {
"lerna": "^7.3.0",
@@ -48,4 +53,4 @@
"@npmcli/node-gyp": "^3.0.0",
"node-gyp": "^9.3.1"
}
}
}
@@ -1,6 +1,6 @@
{
"name": "@nymproject/contract-clients",
"version": "1.2.0-rc.10",
"version": "1.2.0",
"description": "A client for all Nym smart contracts",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
@@ -0,0 +1,133 @@
```ts copy filename="FormattedWalletConnectCode.tsx"
import React from 'react';
import { Coin } from '@cosmjs/stargate';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
// Connect method on Parent Component
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);
};
// Get Balance on Parent Component
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 getClients = async () => {
setClientLoading(true);
try {
setSignerCosmosWasmClient(await fetchSignerCosmosWasmClient(mnemonic));
setSignerClient(await fetchSignerClient(mnemonic));
} catch (error) {
console.error(error);
}
setClientLoading(false);
};
const connect = () => {
getSignerAccount();
getClients();
};
// Get Signner Account on Parent Component
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);
};
export const ConnectWallet = ({
setMnemonic,
connect,
mnemonic,
accountLoading,
clientLoading,
balanceLoading,
account,
balance,
connectButtonText,
}: {
setMnemonic: (value: string) => void;
connect: () => void;
mnemonic: string;
accountLoading: boolean;
clientLoading: boolean;
balanceLoading: boolean;
account: string;
balance: Coin;
connectButtonText: string;
}) => {
return (
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<Typography variant="h5" textAlign="center">
Connect to your account
</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 mnemonic to receive your account information</Typography>
</Box>
)}
</Box>
</Paper>
);
};
```
@@ -0,0 +1,189 @@
```ts copy filename="FormattedWalletDelegationsCode.tsx"
import React from 'react';
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 Table from '@mui/material/Table';
// Get Delegations on parent component
const getDelegations = useCallback(async () => {
const newDelegations = await signerClient.getDelegatorDelegations({
delegator: settings.address,
});
setDelegations(newDelegations);
}, [signerClient]);
// Make a Delegation on parent component
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);
};
// Undelegate All on Parent Component
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);
};
// Withdraw Rewards on Parent Component
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);
};
import React, { useState } from 'react';
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 Table from '@mui/material/Table';
export const Delegations = ({
delegations,
doDelegate,
delegationLoader,
doUndelegateAll,
undeledationLoader,
doWithdrawRewards,
withdrawLoading,
}: {
delegations: any;
doDelegate: ({ mixId, amount }: { mixId: number; amount: number }) => void;
delegationLoader: boolean;
doUndelegateAll: () => void;
undeledationLoader: boolean;
doWithdrawRewards: () => void;
withdrawLoading: boolean;
}) => {
const [delegationNodeId, setDelegationNodeId] = useState<string>();
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
return (
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<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>
);
};
```
@@ -1,384 +0,0 @@
```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,72 @@
```ts copy filename="FormattedWalletSendTokensCode.tsx"
import React, { useState } from 'react';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
// Send tokens on Parent component
const doSendTokens = async (amount: string) => {
const memo = 'test sending tokens';
setSendingTokensLoader(true);
try {
const res = await signerCosmosWasmClient.sendTokens(
account,
recipientAddress,
[{ amount, 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);
};
export const SendTokes = ({
setRecipientAddress,
doSendTokens,
sendingTokensLoader,
}: {
setRecipientAddress: (value: string) => void;
doSendTokens: (amount: string) => void;
sendingTokensLoader: boolean;
}) => {
const [tokensToSend, setTokensToSend] = useState<string>();
return (
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<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(amount)} disabled={sendingTokensLoader}>
{sendingTokensLoader ? 'Sending...' : 'SendTokens'}
</Button>
</Box>
</Box>
</Box>
</Paper>
);
};
```
@@ -0,0 +1,47 @@
import React, { useState } from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
export const GitHubRepoSearch = () => {
const [repoUrl, setRepoUrl] = useState('');
const handleSearch = () => {
if(!repoUrl || repoUrl.length < 1 ) {
return window.alert("Please enter a valid Github URL!")
}
const matchedRepo = repoUrl.match(/https:\/\/github\.com\/(.*)/)[1]
// Construct the search URL
const searchUrl = `https://github.com/search?q=repo:${matchedRepo} fetch(&type=code`;
// Redirect the user to a new search results page
window.open(searchUrl, "_blank");
};
return (
<Box padding={3}>
<Box>
<TextField
type="text"
placeholder="Enter GitHub repo URL: https://github.com/nymtech/nym/"
value={repoUrl}
onChange={(e) => setRepoUrl(e.target.value)}
size="small"
sx={{width: "450px"}}
/>
<Button
variant="outlined"
onClick={handleSearch}
size="medium"
sx={{ marginLeft: 2, marginTop: 0.2 }}
>
Check mixFetch
</Button>
</Box>
</Box>
);
}
+1 -1
View File
@@ -12,6 +12,6 @@ export const NPMLink: FC<{ packageName: string; kind: 'esm' | 'cjs'; preBundled?
sx={{ whiteSpace: 'nowrap', textDecoration: 'none' }}
>
{packageName} <Chip label={kind === 'cjs' ? 'CommonJS' : 'ESM'} size="small" />{' '}
{preBundled && <Chip label="pre-bundled" size="small" color="info" />}
{preBundled && <Chip label="pre-bundled" size="small" color="info" className="chipContained" />}
</Link>
);
+1 -1
View File
@@ -50,7 +50,7 @@ export const Traffic = () => {
await nym?.client.stop();
};
const send = () => nym.client.send({ payload, recipient });
const send = () => payload && recipient && nym?.client.send({ payload, recipient });
useEffect(() => {
init();
-392
View File
@@ -1,392 +0,0 @@
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';
const signerAccount = async (mnemonic) => {
// create a wallet to sign transactions with the mnemonic
const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: 'n',
});
return signer;
};
const fetchSignerCosmosWasmClient = async (mnemonic: string) => {
const signer = await signerAccount(mnemonic);
// 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>
);
};
@@ -0,0 +1,67 @@
import React, { useState, useEffect } from 'react';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
import { useWalletContext } from './utils/wallet.context';
export const ConnectWallet = () => {
const { connect, balance, balanceLoading, accountLoading, account, clientsAreLoading } = useWalletContext();
const [mnemonic, setMnemonic] = useState<string>();
const [connectButtonText, setConnectButtonText] = useState<string>('Connect');
useEffect(() => {
if (accountLoading || clientsAreLoading || balanceLoading) {
setConnectButtonText('Loading...');
} else if (balance) {
setConnectButtonText('Connected');
}
setConnectButtonText('Connect');
}, [accountLoading, clientsAreLoading, balanceLoading]);
return (
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<Typography variant="h5" textAlign="center">
Connect to your testnet account
</Typography>
<Box padding={3}>
<Typography variant="h6">Your testnet 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(mnemonic)}
disabled={!mnemonic || accountLoading || clientsAreLoading || 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 mnemonic to receive your account information</Typography>
</Box>
)}
</Box>
</Paper>
);
};
@@ -0,0 +1,129 @@
import React, { useEffect, useState } from 'react';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
import Alert from '@mui/material/Alert';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import { useWalletContext } from './utils/wallet.context';
export const Delegations = () => {
const { delegations, doDelegate, delegationLoader, unDelegateAll, unDelegateAllLoading, log } = useWalletContext();
const [delegationNodeId, setDelegationNodeId] = useState<string>();
const [amountToBeDelegated, setAmountToBeDelegated] = useState<string>();
const [infoText, setInfoText] = useState<string>('');
const cleanFields = () => {
setDelegationNodeId('');
setAmountToBeDelegated('');
setInfoText('');
};
useEffect(
() => () => {
cleanFields();
},
[],
);
return (
<Box>
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<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(delegationNodeId, amountToBeDelegated);
setInfoText('Changes will be visible after the next epoch');
cleanFields();
}}
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 variant="body2">You do not have delegations</Typography>
) : (
<Box overflow="auto">
<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?.delegations.length > 0 && (
<Box marginBottom={3}>
<Button
variant="outlined"
onClick={() => {
unDelegateAll();
setInfoText('Changes will be visible after the next epoch');
}}
disabled={unDelegateAllLoading}
>
Undelegate All
</Button>
</Box>
)}
{infoText && <Alert severity="info">{infoText}</Alert>}
</Box>
</Box>
</Paper>
{log?.node?.length > 0 && log.type === 'delegate' && (
<Box marginTop={3}>
<Typography variant="h5">Transaction Logs:</Typography>
{log.node}
</Box>
)}
</Box>
);
};
@@ -0,0 +1,69 @@
import React, { useState, useEffect } from 'react';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
import { useWalletContext } from './utils/wallet.context';
export const SendTokes = () => {
const { sendingTokensLoading, sendTokens, log } = useWalletContext();
const [recipientAddress, setRecipientAddress] = useState<string>();
const [tokensToSend, setTokensToSend] = useState<string>();
const cleanFields = () => {
setRecipientAddress('');
setTokensToSend('');
};
useEffect(
() => () => {
cleanFields();
},
[],
);
return (
<Box>
<Paper style={{ marginTop: '1rem', padding: '1rem' }}>
<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={() => {
sendTokens(recipientAddress, tokensToSend);
cleanFields();
}}
disabled={sendingTokensLoading}
>
{sendingTokensLoading ? 'Sending...' : 'Send tokens'}
</Button>
</Box>
</Box>
</Box>
</Paper>
{log?.node?.length > 0 && log.type === 'sendTokens' && (
<Box marginTop={3}>
<Typography variant="h5">Transaction Logs:</Typography>
{log.node}
</Box>
)}
</Box>
);
};
@@ -0,0 +1,262 @@
import React, { createContext, useContext, useState, useCallback, useEffect, useMemo } from 'react';
import { Coin } from '@cosmjs/stargate';
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { settings } from '../../client';
import { signerAccount, fetchSignerCosmosWasmClient, fetchSignerClient } from './wallet.methods';
/**
* This context provides the state for wallet.
*/
interface WalletState {
accountLoading: boolean;
account: string;
clientsAreLoading: boolean;
connect?: (mnemonic: string) => void;
balance?: Coin;
balanceLoading: boolean;
setRecipientAddress?: (value: string) => void;
setTokensToSend?: (value: string) => void;
sendingTokensLoading: boolean;
log?: { type: 'delegate' | 'sendTokens'; node: React.ReactNode[] };
sendTokens?: (recipientAddress: string, tokensToSend: string) => void;
delegations?: any;
doDelegate?: (mixId: string, amount: string) => void;
delegationLoader?: boolean;
unDelegateAll?: () => void;
unDelegateAllLoading?: boolean;
}
export const WalletContext = createContext<WalletState>({
accountLoading: false,
account: '',
clientsAreLoading: false,
balanceLoading: false,
sendingTokensLoading: false,
});
export const useWalletContext = (): React.ContextType<typeof WalletContext> => useContext<WalletState>(WalletContext);
export const WalletContextProvider = ({ children }: { children: JSX.Element }) => {
const [cosmWasmSignerClient, setCosmWasmSignerClient] = useState<SigningCosmWasmClient>(null);
const [nymWasmSignerClient, setNymWasmSignerClient] = useState<any>(null);
const [account, setAccount] = useState<string>('');
const [accountLoading, setAccountLoading] = useState<boolean>(false);
const [delegations, setDelegations] = useState<{ delegations: any[]; start_next_after: any }>();
const [clientsAreLoading, setClientsAreLoading] = useState<boolean>(false);
const [balance, setBalance] = useState<Coin>(null);
const [balanceLoading, setBalanceLoading] = useState<boolean>(false);
const [sendingTokensLoading, setSendingTokensLoading] = useState<boolean>(false);
const [log, setLog] = useState<{ type: 'delegate' | 'sendTokens'; node: React.ReactNode[] }>();
const [delegationLoader, setDelegationLoader] = useState<boolean>(false);
const [unDelegateAllLoading, setUnDelegateAllLoading] = useState<boolean>(false);
const Reset = () => {
setAccountLoading(false);
setDelegations(null);
setClientsAreLoading(false);
setBalance(null);
setBalanceLoading(false);
setSendingTokensLoading(false);
};
const getSignerAccount = async (mnemonic: string) => {
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 (mnemonic: string) => {
setClientsAreLoading(true);
try {
setCosmWasmSignerClient(await fetchSignerCosmosWasmClient(mnemonic));
setNymWasmSignerClient(await fetchSignerClient(mnemonic));
} catch (error) {
console.error(error);
}
setClientsAreLoading(false);
};
const connect = async (mnemonic: string) => {
getSignerAccount(mnemonic);
getClients(mnemonic);
};
const getBalance = useCallback(async () => {
setBalanceLoading(true);
try {
const newBalance = await cosmWasmSignerClient?.getBalance(account, 'unym');
setBalance(newBalance);
} catch (error) {
console.error(error);
}
setBalanceLoading(false);
}, [account, cosmWasmSignerClient]);
const getDelegations = useCallback(async () => {
const delegationsReceived = await nymWasmSignerClient.getDelegatorDelegations({
delegator: settings.address,
});
setDelegations(delegationsReceived);
}, [nymWasmSignerClient]);
const sendTokens = async (recipientAddress: string, tokensToSend: string) => {
const memo: string = 'test sending tokens';
setSendingTokensLoading(true);
try {
const res = await cosmWasmSignerClient.sendTokens(
account,
recipientAddress,
[{ amount: tokensToSend, denom: 'unym' }],
'auto',
memo,
);
setLog({
type: 'sendTokens',
node: [
<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);
}
setSendingTokensLoading(false);
};
const doDelegate = async (mixId: string, amount: string) => {
setDelegationLoader(true);
const memo: string = 'test delegation';
const coinAmount: Coin = { amount, denom: 'unym' };
try {
const res = await nymWasmSignerClient.delegateToMixnode({ mixId: parseInt(mixId, 10) }, 'auto', memo, [
coinAmount,
]);
setLog({
type: 'delegate',
node: [
<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);
};
const unDelegateAll = async () => {
setUnDelegateAllLoading(true);
try {
const logs: React.ReactNode[] = [];
// eslint-disable-next-line no-restricted-syntax
for (const delegation of delegations.delegations) {
// eslint-disable-next-line no-await-in-loop
const res = await nymWasmSignerClient.undelegateFromMixnode({ mixId: delegation.mix_id }, 'auto');
setUnDelegateAllLoading(false);
logs.push(
<div key={JSON.stringify(res, null, 2)}>
<code style={{ marginRight: '2rem' }}>{new Date().toLocaleTimeString()}</code>
<pre>{JSON.stringify(res, null, 2)}</pre>
</div>,
);
}
setLog({
type: 'delegate',
node: logs,
});
} catch (error) {
console.error(error);
setUnDelegateAllLoading(false);
}
};
// const withdrawRewards = async () => {
// const validatorAdress = '';
// const memo = 'test withdraw rewards';
// setWithdrawLoading(true);
// try {
// const res = await cosmWasmSignerClient.withdrawRewards(account, validatorAdress, 'auto', memo);
// setLog({
// type: 'delegate',
// node: [
// <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(
() => () => {
Reset();
},
[],
);
useEffect(() => {
if (cosmWasmSignerClient) {
getBalance();
}
}, [cosmWasmSignerClient]);
useEffect(() => {
if (nymWasmSignerClient) {
getDelegations();
}
}, [nymWasmSignerClient]);
const state = useMemo<WalletState>(
() => ({
accountLoading,
account,
clientsAreLoading,
connect,
balance,
balanceLoading,
sendingTokensLoading,
log,
sendTokens,
delegations,
doDelegate,
delegationLoader,
unDelegateAll,
unDelegateAllLoading,
}),
[
accountLoading,
account,
clientsAreLoading,
connect,
balance,
balanceLoading,
sendingTokensLoading,
log,
sendTokens,
delegations,
doDelegate,
delegationLoader,
unDelegateAll,
unDelegateAllLoading,
],
);
return <WalletContext.Provider value={state}>{children}</WalletContext.Provider>;
};
@@ -0,0 +1,50 @@
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 '../../client';
export const signerAccount = async (mnemonic: string) => {
// create a wallet to sign transactions with the mnemonic
const signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: 'n',
});
return signer;
};
export const fetchSignerCosmosWasmClient = async (mnemonic: string) => {
const signer = await signerAccount(mnemonic);
// 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;
};
export const fetchSignerClient = async (mnemonic: string) => {
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;
};
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/ts-sdk-docs",
"version": "1.2.0-rc.10",
"version": "1.2.0",
"description": "Nym Typescript SDK Docs",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
@@ -28,10 +28,10 @@
"@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.9",
"@nymproject/mix-fetch": "^1.2.0-rc.9",
"@nymproject/mix-fetch-full-fat": "^1.2.0-rc.9",
"@nymproject/sdk-full-fat": "^1.2.0-rc.9",
"@nymproject/contract-clients": ">=1.2.0-rc.10 || ^1",
"@nymproject/mix-fetch": ">=1.2.0-rc.10 || ^1",
"@nymproject/mix-fetch-full-fat": ">=1.2.0-rc.10 || ^1",
"@nymproject/sdk-full-fat": ">=1.2.0-rc.10 || ^1",
"chain-registry": "^1.19.0",
"cosmjs-types": "^0.8.0",
"next": "^13.4.19",
@@ -51,4 +51,4 @@
"typescript": "^4.9.3"
},
"private": false
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"general": "General FAQ"
}
+74
View File
@@ -0,0 +1,74 @@
# Welcome to the TS SDK FAQ!
## How can I interact with Nym?
#### For existing projects:
If you would like to integrate parts of the Nym stack to your existing app, please check out the dedicated [integrations page](../FAQ/integrations).
#### For builders:
###### SDKs
If youre looking to build or Nymify existing solutions, read on: For developing in Rust or TS/JS, then the Nym SDKs are your go-to. Please visit the [Rust SDK documentation](https://nymtech.net/developers/tutorials/rust-sdk.html) for more Rust-related information and tutorials.
Stay on this page, the [TS SDK handbook](../) (you are here) for using the TypeScript SDK.
These SDKs abstract away much of the messaging and core logic from your app, and allow you to run a Nym client as part of your application process, instead of having to run them separately. In short, they simplify building Nym clients into your project.
###### Standalone Nym clients: Websocket, WebAssembly, SOCKS5
Alternatively, you can also use one of the three standalone Nym clients to connect your application to the mixnet.
These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood.
Essentially, they all do the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers. You can learn more about the Nym clients in this [Nym integration page](https://nymtech.net/developers/integrations/mixnet-integration.html).
###### Network requesters:
Network requesters are a type of Service Provider that essentially act as a kind of proxy, somewhat similarly to a Tor exit node. If you have access to a server, you can run a Network Requester, which will perform the following functions:
- Send outbound requests from the local machine through the mixnet to a server;
- The Network Requester then makes a request on the users behalf, shielding the user and their metadata from the untrusted and unknown infrastructure, for example with email or instant messaging client servers;
By default the Network Requester is not an open proxy but rather uses a local and global [allow list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to whitelist host access.
## Which Service Provider to run?
In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. This infrastructure varies depending on the architecture of your application, as well as the endpoints that it needs to hit:
- No Service Provider (Network Requester) needed: If youre running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet;
- Network Requester needed (existing or own): If youre wanting to place the mixnet between your users application instances and a server-based backend, you will need a Network Requester. In this case, if your app supports SOCKS5, you could either use an existing NR or, if your app supports SOCKS5 but needs more extensive whitelisting, you could use the [network requester service provider binary](https://nymtech.net/operators/nodes/network-requester-setup.html) to proxy these requests to your application backend yourself, with the mixnet between the user and your service, in order to prevent metadata leakage being broadcast to the internet.
- Running your own Service Provider: If your usecase is more complex, youre wanting to route RPC requests through the mixnet to a blockchain for example, you will need to look into setting up some sort of Service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications page](https://nymtech.net/developers/community-resources/community-applications-and-guides.html).
## Why gateways?
Nym apps have a stable, potentially long-lasting relation to a gateway node. A client will establish a symmetric key share with a gateway that can be verified on subsequent connection attempts.
Gateways serve a few different functions:
- They act as an end-to-end encrypted message store in case your app goes offline;
- They send encrypted [surb-acks](https://nymtech.net/docs/architecture/traffic-flow.html) for potentially offline recipients, to ensure reliable message delivery;
- They offer a stable addressing location for apps, although the IP may change frequently;
If you want to learn more about gateways, you can check the [mixnet integration page](https://nymtech.net/developers/integrations/mixnet-integration.html).
## Why and when does the mixnet client complain about insufficient topology?
It will in one of the following cases:
- There are empty mix layers - although this is rare;
- The gateway you've registered with does not appear in the network topology -> it is either unbonded or was blacklisted;
- The gateway you want to send packets to does not appear in the network topology -> it is either unbonded or was blacklisted;
To avoid the last two, you need to make sure the gateway you are calling is bonded and whitelisted.
## How can I check whether the gateway I am connecting to is bonded and not blacklisted?
The easiest way of checking what gateway you're registered with is to look at your client address.
Client addresses are in the format of:
`client-id . client-dh @ gateway-id. `
To illustrate this: `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh `
- `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko`: is the client's identity key;
- `ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx`: is the client's Diffie Hellman key;
- `2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh`: is the gateway's identity, which is what you'll need to check the state of the gateway in the [Nym Explorer](https://explorer.nymtech.net/network-components/gateways).
## How can I get my service host whitelisted?
Currently, the different options are:
- You can get it added to the local list of an existing Network Requester;
- You can ask the Nym team to add it to the global [allow list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) if it's not already there;
- You can run your own Network Requester and locally configure it to allow the hosts you need to connect to;
If you'd like to learn more about Network Requesters and the global allow list, you can visit the [network requester set-up page](https://nymtech.net/operators/nodes/network-requester-setup.html).
+4 -2
View File
@@ -1,11 +1,13 @@
{
"index": "Introduction",
"overview": "SDK overview",
"integrations": "Nym integrations",
"installation": "Installation",
"start": "Getting started",
"guides": "Examples",
"examples": "Step-by-step examples",
"playground": "Live Playground",
"bundling": "Bundling",
"FAQ": "FAQ",
"contact": {
"title": "Contact ↗",
@@ -0,0 +1,6 @@
{
"bundling": "General troubleshooting",
"esbuild": "ESbuild",
"webpack": "Webpack"
}
@@ -1,4 +1,4 @@
# Troubleshooting Bundling
# Troubleshooting bundling
You might need some help bundling packages from the Nym Typescript SDK into your package.
@@ -41,7 +41,7 @@ list. Use `[name][ext]` to preserve the output filename, because the package exp
## ESM not supported
If your bundler does not support ECMAScript Modules (ESM) we provide CommonJS packages for most parts of the SDK.
If your bundler does not support ECMAScript Modules (ESM), CommonJS packages are supported 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.
@@ -52,4 +52,3 @@ If you are using a `*-full-fat` package, or if you inline WASM or web workers, y
[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.
@@ -0,0 +1,32 @@
import { Callout } from 'nextra/components';
# Troubleshooting bundling with ESbuild
If you've been following the steps outlined in the Examples section, your development environment should be configured as follows:
#### Environment Setup
Begin by creating a directory and configuring your application environment:
Create your directory and set-up your app environment:
```bash
npm create vite@latest
```
During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands:
```bash
cd < YOUR_APP >
npm i
npm run dev
```
##### Installation
Install the required package:
```bash
npm install @nymproject/< PACKAGE_NAME >
```
<Callout type="info" emoji="️">
Remember that the CosmosKit example will require you to make use of polyfills.
</Callout>
By implementing the provided code for the various components in the step-by-step examples section, you should be able to set-up and run your application without encountering any bundling challenges!
@@ -0,0 +1,93 @@
import { Callout } from 'nextra/components';
# Troubleshooting bundling with Webpack
## Webpack > 5 ESM
For any project using Webpack, you´ll need the following rule in your `webpack.config.js` above version 5:
```json
{
test: /\.(m?js)$/,
resolve: {
fullySpecified: false
}
}
```
### Create-react-app
#### General cases
If you wish to use Webpack for your app with the code provided in the step-by-step examples section, you'll need to:
```bash
npx create-react-app nymapp --template typescript
cd nymapp
```
You'll then need to install the needed dependencies, head to your app's `App.tsx` file and paste the code provided in the step-by-step section.
#### Contract client
<Callout type="info" emoji="️">
Using webpack, the `Contract client` for querying or executing might need polyfills. As create-react-app doesn´t allow you access to the Webpack config without ejecting, you'll overwrite it as follow:
</Callout>
##### Install contract-clients dependencies
```bash
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing
```
Head to you app's `App.tsx` file and replace the code by the one provided in the step-by-step examples section.
##### Polyfilling
Copy the following to your terminal and run:
```bash
npm install react-app-rewired
npm install --save-dev crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url buffer process
cat <<EOF > config-overrides.js
const webpack = require('webpack');
const path = require('path')
module.exports = function override(config) {
const fallback = config.resolve.fallback || {};
Object.assign(fallback, {
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"assert": require.resolve("assert"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"os": require.resolve("os-browserify"),
"url": require.resolve("url")
})
config.resolve.fallback = fallback;
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer']
})
])
config.module.rules = (config.module.rules || []).concat([
{
test: /\.(m?js)$/,
resolve: {
fullySpecified: false
}
}
])
return config;
}
EOF
```
#### Edit the `package.json` file as follows:
```json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
```
@@ -0,0 +1,6 @@
{
"mix-fetch": "1. mixFetch",
"mixnet": "2. Mixnet Client",
"nym-smart-contracts": "3. Nym Smart Contracts",
"cosmos-kit": "4. Cosmos Kit"
}

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