Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77d7fede76 | |||
| f6929e365b | |||
| f98911f904 | |||
| c1be720e49 | |||
| b4a719dbab | |||
| ce99ccea44 | |||
| 595a76ee50 | |||
| dda7c09ec8 | |||
| c27713c678 | |||
| b8125d735e | |||
| 96617469e9 | |||
| 10ade80da0 | |||
| 6c8d5c129d | |||
| 4c01ee0150 | |||
| 9c85d51e34 | |||
| 30dc257ba7 | |||
| 1878720a31 | |||
| ae0d9f3179 | |||
| 1688eb9477 | |||
| 3c1939953c | |||
| 7de43540b9 | |||
| 979244f9da | |||
| 9e555ebd8a | |||
| 579ed7bb6e | |||
| 1655933678 | |||
| cd2978a8b0 | |||
| 6fcdbc15ce | |||
| ceee2d4466 | |||
| 8ee3ec2f4e |
@@ -10,57 +10,61 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04-16-core
|
||||
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: Build
|
||||
run: yarn && yarn build && yarn build:ci:storybook
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/storybook/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy branch to CI www (example)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/example/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example
|
||||
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: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
- 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 pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: true
|
||||
|
||||
- name: Build
|
||||
run: pnpm i && pnpm run build && pnpm run build:ci:storybook
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/storybook/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy branch to CI www (example)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ts-packages/dist/example/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example
|
||||
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: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -21,8 +21,12 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: true
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -35,23 +39,20 @@ jobs:
|
||||
- name: Install wasm-opt
|
||||
uses: ./.github/actions/install-wasm-opt
|
||||
with:
|
||||
version: '116'
|
||||
version: "116"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Build packages
|
||||
run: yarn build:ci
|
||||
run: pnpm run build:ci
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
run: pnpm run lint
|
||||
- name: Typecheck with tsc
|
||||
run: yarn tsc
|
||||
run: pnpm run tsc
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'explorer/**'
|
||||
- "explorer/**"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -14,78 +14,82 @@ jobs:
|
||||
build:
|
||||
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
|
||||
continue-on-error: true
|
||||
- name: Build shared packages
|
||||
run: cd .. && yarn && yarn build
|
||||
- name: Set environment from the example
|
||||
run: cp .env.prod .env
|
||||
# - run: yarn test
|
||||
# continue-on-error: true
|
||||
- run: yarn && yarn build
|
||||
continue-on-error: true
|
||||
- run: yarn storybook:build
|
||||
name: Build storybook
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/dist/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy storybook to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/storybook-static/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ne-sb-${{ 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: network-explorer
|
||||
NYM_PROJECT_NAME: "Network Explorer"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
|
||||
NYM_CI_WWW_LOCATION_STORYBOOK: "ne-sb-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
- name: Deploy
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/dist/"
|
||||
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- 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 pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
continue-on-error: true
|
||||
- name: Build shared packages
|
||||
run: cd .. && yarn && yarn build
|
||||
- name: Set environment from the example
|
||||
run: cp .env.prod .env
|
||||
# - run: yarn test
|
||||
# continue-on-error: true
|
||||
- run: yarn && yarn build
|
||||
continue-on-error: true
|
||||
- run: yarn storybook:build
|
||||
name: Build storybook
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/dist/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy storybook to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/storybook-static/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ne-sb-${{ 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: network-explorer
|
||||
NYM_PROJECT_NAME: "Network Explorer"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
|
||||
NYM_CI_WWW_LOCATION_STORYBOOK: "ne-sb-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
- name: Deploy
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/dist/"
|
||||
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
|
||||
@@ -3,72 +3,75 @@ name: ci-nym-wallet-storybook
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'nym-wallet/**'
|
||||
- "nym-wallet/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
continue-on-error: true
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
continue-on-error: true
|
||||
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: true
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- 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
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Build dependencies
|
||||
run: yarn && yarn build
|
||||
- name: Build dependencies
|
||||
run: pnpm build
|
||||
|
||||
- name: Build storybook
|
||||
run: yarn storybook:build
|
||||
working-directory: ./nym-wallet
|
||||
- name: Build storybook
|
||||
run: pnpm run storybook:build
|
||||
working-directory: ./wallet
|
||||
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "nym-wallet/storybook-static/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "nym-wallet/storybook-static/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
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 - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nym-wallet
|
||||
NYM_PROJECT_NAME: "nym-wallet"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nym-wallet
|
||||
NYM_PROJECT_NAME: "nym-wallet"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -17,18 +17,23 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.17
|
||||
node-version: 18
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
@@ -36,16 +41,15 @@ jobs:
|
||||
- name: Install wasm-opt
|
||||
uses: ./.github/actions/install-wasm-opt
|
||||
with:
|
||||
version: '116'
|
||||
version: "116"
|
||||
|
||||
- name: Build branch WASM packages
|
||||
run: make sdk-wasm-build
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
- name: Build
|
||||
run: yarn docs:prod:build
|
||||
- name: Deploy branch to CI www (docs)
|
||||
run: pnpm docs:prod:build
|
||||
|
||||
- name: Deploy branch to CI www (docs)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
@@ -57,7 +61,7 @@ jobs:
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
run: pnpm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
@@ -75,4 +79,4 @@ jobs:
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -61,12 +61,12 @@ jobs:
|
||||
- name: Create env file
|
||||
uses: timheuer/base64-to-file@v1.2
|
||||
with:
|
||||
fileName: '.env'
|
||||
fileName: ".env"
|
||||
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||
|
||||
- name: Install project dependencies
|
||||
shell: bash
|
||||
run: cd .. && yarn --network-timeout 100000
|
||||
run: cd .. && pnpm i --network-timeout 100000
|
||||
|
||||
- name: Install app dependencies and build it
|
||||
env:
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
run: yarn && yarn build
|
||||
run: pnpm i && pnpm build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/${{ github.ref_name }}/nym-wallet
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
Generated
+7
-9
@@ -2221,7 +2221,7 @@ dependencies = [
|
||||
"atomic 0.6.0",
|
||||
"pear",
|
||||
"serde",
|
||||
"toml 0.8.14",
|
||||
"toml 0.8.15",
|
||||
"uncased",
|
||||
"version_check",
|
||||
]
|
||||
@@ -5037,7 +5037,7 @@ dependencies = [
|
||||
"sysinfo 0.30.12",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml 0.8.14",
|
||||
"toml 0.8.15",
|
||||
"tracing",
|
||||
"url",
|
||||
"zeroize",
|
||||
@@ -5742,7 +5742,6 @@ name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"defguard_wireguard_rs",
|
||||
"ip_network",
|
||||
@@ -5751,7 +5750,6 @@ dependencies = [
|
||||
"nym-network-defaults",
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"x25519-dalek",
|
||||
@@ -8628,14 +8626,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.14"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||
checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.14",
|
||||
"toml_edit 0.22.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8662,9 +8660,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.14"
|
||||
version = "0.22.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
|
||||
@@ -107,12 +107,12 @@ sdk-wasm-build:
|
||||
$(MAKE) -C wasm/zknym-lib
|
||||
#$(MAKE) -C wasm/full-nym-wasm
|
||||
|
||||
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
||||
# run this from npm/pnpm to ensure tools are in the path, e.g. pnpm build:sdk from root of repo
|
||||
sdk-typescript-build:
|
||||
npx lerna run --scope @nymproject/sdk build --stream
|
||||
npx lerna run --scope @nymproject/mix-fetch build --stream
|
||||
npx lerna run --scope @nymproject/node-tester build --stream
|
||||
yarn --cwd sdk/typescript/codegen/contract-clients build
|
||||
pnpm run --filter @nymproject/sdk --stream build
|
||||
pnpm run --filter @nymproject/mix-fetch --stream build
|
||||
pnpm run --filter @nymproject/node-tester --stream build
|
||||
pnpm --cwd sdk/typescript/codegen/contract-clients build
|
||||
|
||||
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
|
||||
WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
|
||||
@@ -170,10 +170,10 @@ build-nym-mixnode:
|
||||
|
||||
generate-typescript:
|
||||
cd tools/ts-rs-cli && cargo run && cd ../..
|
||||
yarn types:lint:fix
|
||||
pnpm types:lint:fix
|
||||
|
||||
run-api-tests:
|
||||
cd nym-api/tests/functional_test && yarn test:qa
|
||||
cd nym-api/tests/functional_test && pnpm test:qa
|
||||
|
||||
# Build debian package, and update PPA
|
||||
deb-mixnode: build-nym-mixnode
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage, PeerPublicKey};
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
@@ -57,19 +57,6 @@ impl AuthenticatorRequest {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
@@ -80,5 +67,4 @@ impl AuthenticatorRequest {
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_wireguard_types::registration::RegistrationData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
@@ -33,31 +33,10 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
pub fn new_registered(reply_to: Recipient, request_id: u64) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
@@ -85,7 +64,6 @@ impl AuthenticatorResponse {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +72,6 @@ impl AuthenticatorResponse {
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -108,12 +85,4 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
@@ -27,15 +27,12 @@ pub type HmacSha256 = Hmac<Sha256>;
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub enum ClientMessage {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
Query(PeerPublicKey),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -63,19 +60,6 @@ pub struct RegistrationData {
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: u64,
|
||||
pub suspended: bool,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
||||
@@ -12,7 +12,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
defguard_wireguard_rs = { workspace = true }
|
||||
# The latest version on crates.io at the time of writing this (6.0.0) has a
|
||||
@@ -25,6 +24,5 @@ nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-stream = { workspace = true }
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("peers in wireguard don't match with in-memory ")]
|
||||
PeerMismatch,
|
||||
|
||||
#[error("{0}")]
|
||||
Defguard(#[from] defguard_wireguard_rs::error::WireguardInterfaceError),
|
||||
}
|
||||
+14
-25
@@ -1,6 +1,3 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
// #![warn(clippy::pedantic)]
|
||||
// #![warn(clippy::expect_used)]
|
||||
@@ -9,14 +6,13 @@
|
||||
use dashmap::DashMap;
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi};
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry, PeerPublicKey};
|
||||
use peer_controller::PeerControlRequest;
|
||||
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry};
|
||||
use peer_controller::PeerControlMessage;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver};
|
||||
|
||||
const WG_TUN_NAME: &str = "nymwg";
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod peer_controller;
|
||||
|
||||
pub struct WgApiWrapper {
|
||||
@@ -43,14 +39,14 @@ pub struct WireguardGatewayData {
|
||||
config: Config,
|
||||
keypair: Arc<KeyPair>,
|
||||
client_registry: Arc<GatewayClientRegistry>,
|
||||
peer_tx: UnboundedSender<PeerControlRequest>,
|
||||
peer_tx: mpsc::UnboundedSender<PeerControlMessage>,
|
||||
}
|
||||
|
||||
impl WireguardGatewayData {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
keypair: Arc<KeyPair>,
|
||||
) -> (Self, UnboundedReceiver<PeerControlRequest>) {
|
||||
) -> (Self, mpsc::UnboundedReceiver<PeerControlMessage>) {
|
||||
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
|
||||
(
|
||||
WireguardGatewayData {
|
||||
@@ -79,26 +75,20 @@ impl WireguardGatewayData {
|
||||
let mut peer = Peer::new(Key::new(client.pub_key.to_bytes()));
|
||||
peer.allowed_ips
|
||||
.push(IpAddrMask::new(client.private_ip, 32));
|
||||
let msg = PeerControlRequest::AddPeer(peer);
|
||||
let msg = PeerControlMessage::AddPeer(peer);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
|
||||
pub fn remove_peer(&self, client: &GatewayClient) -> Result<(), Error> {
|
||||
let key = Key::new(client.pub_key().to_bytes());
|
||||
let msg = PeerControlRequest::RemovePeer(key);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
|
||||
pub fn query_bandwidth(&self, peer_public_key: PeerPublicKey) -> Result<(), Error> {
|
||||
let key = Key::new(peer_public_key.to_bytes());
|
||||
let msg = PeerControlRequest::QueryBandwidth(key);
|
||||
let msg = PeerControlMessage::RemovePeer(key);
|
||||
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WireguardData {
|
||||
pub inner: WireguardGatewayData,
|
||||
pub peer_rx: UnboundedReceiver<PeerControlRequest>,
|
||||
pub peer_rx: UnboundedReceiver<PeerControlMessage>,
|
||||
}
|
||||
|
||||
/// Start wireguard device
|
||||
@@ -106,7 +96,6 @@ pub struct WireguardData {
|
||||
pub async fn start_wireguard(
|
||||
task_client: nym_task::TaskClient,
|
||||
wireguard_data: WireguardData,
|
||||
control_tx: UnboundedSender<peer_controller::PeerControlResponse>,
|
||||
) -> Result<std::sync::Arc<WgApiWrapper>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi};
|
||||
@@ -146,13 +135,13 @@ pub async fn start_wireguard(
|
||||
wg_api.configure_peer_routing(&[catch_all_peer])?;
|
||||
|
||||
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
|
||||
let mut controller = PeerController::new(
|
||||
wg_api.clone(),
|
||||
interface_config.peers,
|
||||
wireguard_data.peer_rx,
|
||||
control_tx,
|
||||
);
|
||||
let mut controller = PeerController::new(wg_api.clone(), wireguard_data.peer_rx);
|
||||
tokio::spawn(async move { controller.run(task_client).await });
|
||||
|
||||
Ok(wg_api)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub async fn start_wireguard() {
|
||||
todo!("WireGuard is currently only supported on Linux");
|
||||
}
|
||||
|
||||
@@ -1,182 +1,91 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use chrono::{Timelike, Utc};
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, WireguardInterfaceApi};
|
||||
use nym_wireguard_types::registration::{RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY};
|
||||
use std::time::SystemTime;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use defguard_wireguard_rs::{
|
||||
host::{Host, Peer},
|
||||
key::Key,
|
||||
WGApi, WireguardInterfaceApi,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::WgApiWrapper;
|
||||
|
||||
// To avoid any problems, keep this stale check time bigger (>2x) then the bandwidth cap
|
||||
// reset time (currently that one is 24h, at UTC midnight)
|
||||
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days
|
||||
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60); // 1 hour
|
||||
const DEFAULT_PEER_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
|
||||
|
||||
pub enum PeerControlRequest {
|
||||
pub enum PeerControlMessage {
|
||||
AddPeer(Peer),
|
||||
RemovePeer(Key),
|
||||
QueryBandwidth(Key),
|
||||
}
|
||||
|
||||
pub enum PeerControlResponse {
|
||||
AddPeer {
|
||||
success: bool,
|
||||
},
|
||||
RemovePeer {
|
||||
success: bool,
|
||||
},
|
||||
QueryBandwidth {
|
||||
bandwidth_data: Option<RemainingBandwidthData>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct PeerController {
|
||||
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
|
||||
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
active_peers: HashMap<Key, Peer>,
|
||||
suspended_peers: HashMap<Key, Peer>,
|
||||
last_seen_bandwidth: HashMap<Key, u64>,
|
||||
}
|
||||
|
||||
impl PeerController {
|
||||
pub fn new(
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
peers: Vec<Peer>,
|
||||
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
|
||||
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
) -> Self {
|
||||
let timeout_check_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
|
||||
);
|
||||
let active_peers = peers
|
||||
.into_iter()
|
||||
.map(|peer| (peer.public_key.clone(), peer))
|
||||
.collect();
|
||||
|
||||
PeerController {
|
||||
wg_api,
|
||||
request_rx,
|
||||
response_tx,
|
||||
peer_rx,
|
||||
timeout_check_interval,
|
||||
active_peers,
|
||||
suspended_peers: HashMap::new(),
|
||||
last_seen_bandwidth: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_stale_peer(&self, peer: &Peer, current_timestamp: SystemTime) -> Result<bool, Error> {
|
||||
if let Some(timestamp) = peer.last_handshake {
|
||||
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
|
||||
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
|
||||
self.wg_api.inner.remove_peer(&peer.public_key)?;
|
||||
return Ok(true);
|
||||
fn remove_stale_peers(wg_api: &WGApi, host: Host) {
|
||||
let current_timestamp = SystemTime::now();
|
||||
for (key, peer) in host.peers.iter() {
|
||||
if let Some(timestamp) = peer.last_handshake {
|
||||
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
|
||||
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
|
||||
if let Err(e) = wg_api.remove_peer(key) {
|
||||
log::error!("Could not remove stale peer: {:?}", e);
|
||||
} else {
|
||||
log::debug!("Removed stale peer {:?}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn check_suspend_peer(&mut self, peer: &Peer) -> Result<(), Error> {
|
||||
let prev_peer = self
|
||||
.active_peers
|
||||
.get(&peer.public_key)
|
||||
.ok_or(Error::PeerMismatch)?;
|
||||
let data_usage =
|
||||
(peer.rx_bytes + peer.tx_bytes).saturating_sub(prev_peer.rx_bytes + prev_peer.tx_bytes);
|
||||
if data_usage > BANDWIDTH_CAP_PER_DAY {
|
||||
self.wg_api.inner.remove_peer(&peer.public_key)?;
|
||||
let (moved_key, moved_peer) = self
|
||||
.active_peers
|
||||
.remove_entry(&peer.public_key)
|
||||
.ok_or(Error::PeerMismatch)?;
|
||||
self.suspended_peers.insert(moved_key, moved_peer);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_peers(&mut self) -> Result<(), Error> {
|
||||
// Add 10 seconds to cover edge cases. At worst, we give ten free seconds worth of bandwidth
|
||||
// by resetting the bandwidth twice
|
||||
let reset = Utc::now().num_seconds_from_midnight() as u64
|
||||
<= DEFAULT_PEER_TIMEOUT_CHECK.as_secs() + 10;
|
||||
|
||||
if reset {
|
||||
for (_, peer) in self.suspended_peers.drain() {
|
||||
self.wg_api.inner.configure_peer(&peer)?;
|
||||
}
|
||||
}
|
||||
let host = self.wg_api.inner.read_interface_data()?;
|
||||
self.last_seen_bandwidth = host
|
||||
.peers
|
||||
.iter()
|
||||
.map(|(key, peer)| (key.clone(), peer.rx_bytes + peer.tx_bytes))
|
||||
.collect();
|
||||
if reset {
|
||||
self.active_peers = host.peers;
|
||||
} else {
|
||||
let current_timestamp = SystemTime::now();
|
||||
for peer in host.peers.values() {
|
||||
if !self.check_stale_peer(peer, current_timestamp)? {
|
||||
self.check_suspend_peer(peer)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self, mut task_client: nym_task::TaskClient) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.timeout_check_interval.next() => {
|
||||
if let Err(e) = self.check_peers() {
|
||||
log::error!("Error while periodically checking peers: {:?}", e);
|
||||
match self.wg_api.inner.read_interface_data() {
|
||||
Ok(host) => Self::remove_stale_peers(&self.wg_api.inner, host),
|
||||
Err(e) => { log::error!("Could not read peer data: {:?}", e); },
|
||||
}
|
||||
}
|
||||
_ = task_client.recv() => {
|
||||
log::trace!("PeerController handler: Received shutdown");
|
||||
break;
|
||||
}
|
||||
msg = self.request_rx.recv() => {
|
||||
msg = self.peer_rx.recv() => {
|
||||
match msg {
|
||||
Some(PeerControlRequest::AddPeer(peer)) => {
|
||||
let success = if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
|
||||
Some(PeerControlMessage::AddPeer(peer)) => {
|
||||
if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
|
||||
log::error!("Could not configure peer: {:?}", e);
|
||||
false
|
||||
} else {
|
||||
self.active_peers.insert(peer.public_key.clone(), peer);
|
||||
true
|
||||
};
|
||||
self.response_tx.send(PeerControlResponse::AddPeer { success }).ok();
|
||||
}
|
||||
}
|
||||
Some(PeerControlRequest::RemovePeer(peer_pubkey)) => {
|
||||
let success = if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
|
||||
Some(PeerControlMessage::RemovePeer(peer_pubkey)) => {
|
||||
if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
|
||||
log::error!("Could not remove peer: {:?}", e);
|
||||
false
|
||||
} else {
|
||||
self.active_peers.remove(&peer_pubkey);
|
||||
self.suspended_peers.remove(&peer_pubkey);
|
||||
true
|
||||
};
|
||||
self.response_tx.send(PeerControlResponse::RemovePeer { success }).ok();
|
||||
}
|
||||
Some(PeerControlRequest::QueryBandwidth(peer_pubkey)) => {
|
||||
let msg = if self.suspended_peers.contains_key(&peer_pubkey) {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: 0, suspended: true }) }
|
||||
} else if let Some(&consumed_bandwidth) = self.last_seen_bandwidth.get(&peer_pubkey) {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: BANDWIDTH_CAP_PER_DAY - consumed_bandwidth, suspended: false })}
|
||||
} else {
|
||||
PeerControlResponse::QueryBandwidth { bandwidth_data: None }
|
||||
};
|
||||
self.response_tx.send(msg).ok();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::trace!("PeerController [main loop]: stopping since channel closed");
|
||||
|
||||
@@ -8,19 +8,3 @@
|
||||
[HostSailor](https://hostsailor.com),"USA","Yes, based on ticket","Yes","The IPv6 setup needs custom research and is not documented","05/2024"
|
||||
[Misaka](https://www.misaka.io/),"South Africa","Yes, native support","No","Very Expensive","05/2024"
|
||||
[IsHosting](https://ishosting.com/en),"Brazil, Netherlands","Yes, based on ticket","Yes","Expensive","05/2024"
|
||||
[AlexHost](https://alexhost.com),"Moldova, Bulgaria, Sweden, Netherlands","Yes, on by default","Yes","They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR)","07/2024"
|
||||
[iHostArt](https://ihostart.com),"Romania","Yes, on by default","Yes","Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware.","07/2024"
|
||||
[Incognet](https://incognet.io),"Netherlands and USA","Yes, on by default","Yes","They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits","07/2024"
|
||||
[vSys Host](https://vsys.host),"Ukraine, Netherlands, USA","Yes, on by default","Yes","Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check.","07/2024"
|
||||
[LiteServer](https://liteserver.nl),"Netherlands","Yes, on by default","Yes","Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal","07/2024"
|
||||
[TerraHost](https://terrahost.no),"Norway","Yes, on by default","Yes","Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser","07/2024"
|
||||
[Mevspace](https://mevspace.com),"Poland","Yes, on by default","Yes","Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff","07/2024"
|
||||
[Hostiko](https://hostiko.com.ua),"Ukraine, Germany","Yes, on by default","Yes","Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket.","07/2024"
|
||||
[Hostslick](https://hostslick.com),"Netherlands, Germany","Yes, on by default","Yes","Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node","07/2024"
|
||||
[RDP](https://rdp.sh),"Netherlands, USA, Poland","Yes, on by default","Yes","German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node.","07/2024"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -200,58 +200,9 @@ systemctl status <NODE>.service
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
This lets your operating system know it's OK to reload the service configuration. Then restart your `<NODE>`.
|
||||
This lets your operating system know it's ok to reload the service configuration. Then restart your `<NODE>`.
|
||||
|
||||
|
||||
## Backup a node
|
||||
|
||||
Anything can happen to the server on which your node is running. To back up your `nym-node` keys and configuration protects the operators against the negative impact of unexpected events. To restart your node on another server, two essential pieces are needed:
|
||||
|
||||
1. Node keys to initialise the same node on a new VPS
|
||||
2. Access to the bonding Nym account (wallet seeds) to edit the IP on smart contract
|
||||
|
||||
Assuming that everyone access their wallets from local machine and does *not* store their seeds on VPS, point \#2 should be a given. To backup your `nym-node` keys and configuration in the easiest way possible, copy the entire config directory `.nym` from your VPS to your local desktop, using a special copy command `scp`:
|
||||
|
||||
1. Create a directory where you want to store your backup
|
||||
```sh
|
||||
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
2. Copy configuration folder `.nym` from your VPS to your newly created backup directory:
|
||||
|
||||
```sh
|
||||
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<YOUR_NODE_ID> <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
3. The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup directory.
|
||||
|
||||
## Restoring a node
|
||||
|
||||
In case your VPS shut down and you have a [backup](#backup-a-node) of your node keys and access to your bonding wallet, you can easily restore your node on another server without losing your delegation.
|
||||
|
||||
1. On VPS: Do all [preliminary steps](preliminary-steps.md) needed to run a `nym-node`.
|
||||
|
||||
2. On VPS: Create a `.nym/nym-nodes` configuration folder:
|
||||
```sh
|
||||
mkdir -pv ~/.nym/nym-nodes
|
||||
```
|
||||
|
||||
3. From machine where your node is backed up (usually local desktop): Copy the folder with your node keys and configuration to the newly created folder on your VPS using `scp` command. Make sure to grab the entire `nym-node` configuration folder, which is called after your local `nym-node` ID, the `-r` flag will take care of all sub-directories and their content:
|
||||
```sh
|
||||
scp -r <LOCAL_NODE_CONFIGURATION_FOLDER> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:~/.nym/nym-nodes/
|
||||
```
|
||||
|
||||
4. The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the target directory `.nym/nym-nodes` on your VPS.
|
||||
|
||||
|
||||
5. Configure your node on the new VPS:
|
||||
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = ['<YOUR_PUBLIC_IP>',]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
|
||||
* Try to run the node and see if everything works.
|
||||
* Setup the [systemd](#systemd) automation (don't forget to add the [terms and conditions flag](setup.md#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
|
||||
|
||||
6. And finally change the node smart contract info via the wallet interface. Open Nym Wallet, go to *Bonding*, open *Gateway Settings* or *Mixnode Settings* and change *Host* value to the new `nym-node` IP address. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks, returning zero performance.
|
||||
|
||||
## Moving a node
|
||||
|
||||
In case of a need to move a Nym Node from one machine to another and avoiding to lose the delegation, here are few steps how to do it.
|
||||
@@ -273,21 +224,12 @@ mkdir ~/.nym/nym-nodes
|
||||
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:~/.nym/nym-nodes/
|
||||
```
|
||||
|
||||
**On new/target VPS**
|
||||
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = ['<YOUR_PUBLIC_IP>',]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
|
||||
* Try to run the node and see if everything works.
|
||||
* Setup the [systemd](#systemd) automation (don't forget to add the [terms and conditions flag](setup.md#terms--conditions)) to `ExecStart` command, reload the daemon and run the service. If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic:
|
||||
```sh
|
||||
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/systemd/system/nym-node.service <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:/etc/systemd/system/nym-node.service
|
||||
```
|
||||
|
||||
**In your desktop wallet**
|
||||
**On new/target machine**
|
||||
|
||||
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP. You can get that one running a command `echo "$(curl -4 https://ifconfig.me)"`.
|
||||
* Setup the [systemd](#systemd) automation, reload the daemon and run the service, or just simply run the node if you don't use automation
|
||||
* Change the node smart contract info via the wallet interface. Open Nym Wallet, go to *Bonding*, open *Gateway Settings* or *Mixnode Settings* and change *Host* value to the new `nym-node` IP address. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks, returning zero performance.
|
||||
|
||||
Make sure to stop the old node.
|
||||
|
||||
## Rename node local ID
|
||||
|
||||
Local node ID (not the identity key) is a name chosen by operators which defines where the nodes configuration data will be stored, where the ID determines the path to `~/.nym/nym-nodes/<ID>/`. This ID is never shared on the network.
|
||||
|
||||
@@ -592,28 +592,15 @@ server {
|
||||
|
||||
# WSS configuration
|
||||
server {
|
||||
listen ${port_value} ssl http2;
|
||||
listen [::]:${port_value} ssl http2;
|
||||
|
||||
server_name ${host_name};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${host_name}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${host_name}/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
listen ${port_value};
|
||||
location / {
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header X-Forwarded-For \$remote_addr;
|
||||
@@ -712,9 +699,9 @@ We made the landing page customization directory in [*Preliminary steps*](#preli
|
||||
3. Configure your site to work with `nginx`. Open a new text file `/etc/nginx/sites-available/<HOSTNAME>` and paste the block below. Don't forget to insert your correct values.
|
||||
~~~admonish example collapsible=true title="Site configuration `/etc/nginx/sites-available/<HOSTNAME>`"
|
||||
```bash
|
||||
#####################################################
|
||||
# EXCHANGE ALL <HOSTNAME> & <WSS_PORT> VARIABLES ! #
|
||||
####################################################
|
||||
#################################################################
|
||||
# EXCHANGE ALL <HOSTNAME>, <WSS_PORT> AND <PATH_TO> VARIABLES ! #
|
||||
#################################################################
|
||||
|
||||
# Reversed proxy configuration for landing page
|
||||
server {
|
||||
@@ -757,27 +744,15 @@ server {
|
||||
|
||||
# WSS configuration
|
||||
server {
|
||||
listen <WSS_PORT> ssl http2;
|
||||
listen [::]:<WSS_PORT> ssl http2;
|
||||
|
||||
server_name <HOSTNAME>;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/<HOSTNAME>/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/<HOSTNAME>/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
listen <WSS_PORT>;
|
||||
location / {
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
add_header 'Access-Control-Allow-Headers' '*';
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals"]
|
||||
"extends": ["next/core-web-vitals"],
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Navbar } from './components/Nav/Navbar'
|
||||
import { Providers } from './providers'
|
||||
import React from "react";
|
||||
import { Navbar } from "./components/Nav/Navbar";
|
||||
import { Providers } from "./providers";
|
||||
|
||||
const App = ({ children }: { children: React.ReactNode }) => (
|
||||
const App = ({ children }: { children: any }) => (
|
||||
<Providers>
|
||||
<Navbar>{children}</Navbar>
|
||||
</Providers>
|
||||
)
|
||||
);
|
||||
|
||||
export { App }
|
||||
export { App };
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Typography } from '@mui/material';
|
||||
import * as React from 'react';
|
||||
import { Typography } from "@mui/material";
|
||||
import * as React from "react";
|
||||
|
||||
export const ComponentError: FCWithChildren<{ text: string }> = ({ text }) => (
|
||||
export const ComponentError = ({ text }: { text: string }) => (
|
||||
<Typography
|
||||
sx={{ marginTop: 2, color: 'primary.main', fontSize: 10 }}
|
||||
sx={{ marginTop: 2, color: "primary.main", fontSize: 10 }}
|
||||
variant="body1"
|
||||
data-testid="delegation-total-amount"
|
||||
>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Card, CardHeader, CardContent, Typography } from '@mui/material'
|
||||
import React, { ReactEventHandler } from 'react'
|
||||
import React, { ReactEventHandler } from "react";
|
||||
import { Card, CardHeader, CardContent, Typography } from "@mui/material";
|
||||
|
||||
type ContentCardProps = {
|
||||
title?: React.ReactNode
|
||||
subtitle?: string
|
||||
Icon?: React.ReactNode
|
||||
Action?: React.ReactNode
|
||||
errorMsg?: string
|
||||
onClick?: ReactEventHandler
|
||||
}
|
||||
title?: React.ReactNode;
|
||||
subtitle?: string;
|
||||
Icon?: React.ReactNode;
|
||||
Action?: React.ReactNode;
|
||||
errorMsg?: string;
|
||||
children: React.ReactNode;
|
||||
onClick?: ReactEventHandler;
|
||||
};
|
||||
|
||||
export const ContentCard: FCWithChildren<ContentCardProps> = ({
|
||||
export const ContentCard = ({
|
||||
title,
|
||||
Icon,
|
||||
Action,
|
||||
@@ -18,11 +19,11 @@ export const ContentCard: FCWithChildren<ContentCardProps> = ({
|
||||
errorMsg,
|
||||
children,
|
||||
onClick,
|
||||
}) => (
|
||||
<Card onClick={onClick} sx={{ height: '100%' }}>
|
||||
}: ContentCardProps) => (
|
||||
<Card onClick={onClick} sx={{ height: "100%" }}>
|
||||
{title && (
|
||||
<CardHeader
|
||||
title={title || ''}
|
||||
title={title || ""}
|
||||
avatar={Icon}
|
||||
action={Action}
|
||||
subheader={subtitle}
|
||||
@@ -30,9 +31,9 @@ export const ContentCard: FCWithChildren<ContentCardProps> = ({
|
||||
)}
|
||||
{children && <CardContent>{children}</CardContent>}
|
||||
{errorMsg && (
|
||||
<Typography variant="body2" sx={{ color: 'danger', padding: 2 }}>
|
||||
<Typography variant="body2" sx={{ color: "danger", padding: 2 }}>
|
||||
{errorMsg}
|
||||
</Typography>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as React from 'react'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip'
|
||||
import * as React from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Tooltip } from "@nymproject/react";
|
||||
import { InfoOutlined } from "@mui/icons-material";
|
||||
|
||||
export const CustomColumnHeading: FCWithChildren<{
|
||||
headingTitle: string
|
||||
tooltipInfo?: string
|
||||
headingTitle: string;
|
||||
tooltipInfo?: string;
|
||||
}> = ({ headingTitle, tooltipInfo }) => {
|
||||
const theme = useTheme()
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box alignItems="center" display="flex">
|
||||
@@ -20,11 +21,12 @@ export const CustomColumnHeading: FCWithChildren<{
|
||||
bgColor={theme.palette.nym.networkExplorer.tooltip.background}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
TooltipIcon={<InfoOutlined sx={{ width: 18, height: 18, mr: 1 }} />}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="body2" fontWeight={600} data-testid={headingTitle}>
|
||||
{headingTitle}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { Box, SxProps } from '@mui/material'
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'
|
||||
import { CurrencyDenom, DecCoin } from '@nymproject/types'
|
||||
import { useWalletContext } from '@/app/context/wallet'
|
||||
import { urls } from '@/app/utils'
|
||||
import { useDelegationsContext } from '@/app/context/delegations'
|
||||
import { validateAmount } from '@/app/utils/currency'
|
||||
import { SimpleModal } from './SimpleModal'
|
||||
import { ModalListItem } from './ModalListItem'
|
||||
import { DelegationModalProps } from './DelegationModal'
|
||||
import React, { useState } from "react";
|
||||
import { Box, SxProps } from "@mui/material";
|
||||
import { IdentityKeyFormField } from "@nymproject/react";
|
||||
import { CurrencyFormField } from "@nymproject/react";
|
||||
import { CurrencyDenom, DecCoin } from "@nymproject/types";
|
||||
import { useWalletContext } from "@/app/context/wallet";
|
||||
import { urls } from "@/app/utils";
|
||||
import { useDelegationsContext } from "@/app/context/delegations";
|
||||
import { validateAmount } from "@/app/utils/currency";
|
||||
import { SimpleModal } from "./SimpleModal";
|
||||
import { ModalListItem } from "./ModalListItem";
|
||||
import { DelegationModalProps } from "./DelegationModal";
|
||||
|
||||
const MIN_AMOUNT_TO_DELEGATE = 10
|
||||
const MIN_AMOUNT_TO_DELEGATE = 10;
|
||||
|
||||
type Props = {
|
||||
mixId: number
|
||||
identityKey: string
|
||||
header?: string
|
||||
buttonText?: string
|
||||
rewardInterval?: string
|
||||
estimatedReward?: number
|
||||
profitMarginPercentage?: string | null
|
||||
nodeUptimePercentage?: number | null
|
||||
denom: CurrencyDenom
|
||||
sx?: SxProps
|
||||
backdropProps?: object
|
||||
onClose: () => void
|
||||
onOk?: (delegationModalProps: DelegationModalProps) => void
|
||||
}
|
||||
mixId: number;
|
||||
identityKey: string;
|
||||
header?: string;
|
||||
buttonText?: string;
|
||||
rewardInterval?: string;
|
||||
estimatedReward?: number;
|
||||
profitMarginPercentage?: string | null;
|
||||
nodeUptimePercentage?: number | null;
|
||||
denom: CurrencyDenom;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
onClose: () => void;
|
||||
onOk?: (delegationModalProps: DelegationModalProps) => void;
|
||||
};
|
||||
|
||||
export const DelegateModal = ({
|
||||
mixId,
|
||||
@@ -40,106 +40,106 @@ export const DelegateModal = ({
|
||||
sx,
|
||||
}: Props) => {
|
||||
const [amount, setAmount] = useState<DecCoin | undefined>({
|
||||
amount: '10',
|
||||
denom: 'nym',
|
||||
})
|
||||
const [isValidated, setValidated] = useState<boolean>(false)
|
||||
const [errorAmount, setErrorAmount] = useState<string | undefined>()
|
||||
amount: "10",
|
||||
denom: "nym",
|
||||
});
|
||||
const [isValidated, setValidated] = useState<boolean>(false);
|
||||
const [errorAmount, setErrorAmount] = useState<string | undefined>();
|
||||
|
||||
const { address, balance } = useWalletContext()
|
||||
const { handleDelegate } = useDelegationsContext()
|
||||
const { address, balance } = useWalletContext();
|
||||
const { handleDelegate } = useDelegationsContext();
|
||||
|
||||
const validate = async () => {
|
||||
let newValidatedValue = true
|
||||
let errorAmountMessage
|
||||
let newValidatedValue = true;
|
||||
let errorAmountMessage;
|
||||
|
||||
if (amount && !(await validateAmount(amount.amount, '0'))) {
|
||||
newValidatedValue = false
|
||||
errorAmountMessage = 'Please enter a valid amount'
|
||||
if (amount && !(await validateAmount(amount.amount, "0"))) {
|
||||
newValidatedValue = false;
|
||||
errorAmountMessage = "Please enter a valid amount";
|
||||
}
|
||||
|
||||
if (amount && +amount.amount < MIN_AMOUNT_TO_DELEGATE) {
|
||||
errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${denom.toUpperCase()}`
|
||||
newValidatedValue = false
|
||||
errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${denom.toUpperCase()}`;
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
if (!amount?.amount.length) {
|
||||
newValidatedValue = false
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
if (amount && balance.data && +balance.data - +amount.amount <= 0) {
|
||||
errorAmountMessage = 'Not enough funds'
|
||||
newValidatedValue = false
|
||||
errorAmountMessage = "Not enough funds";
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
setErrorAmount(errorAmountMessage)
|
||||
setValidated(newValidatedValue)
|
||||
}
|
||||
setErrorAmount(errorAmountMessage);
|
||||
setValidated(newValidatedValue);
|
||||
};
|
||||
|
||||
const delegateToMixnode = async ({
|
||||
delegationMixId,
|
||||
delegationAmount,
|
||||
}: {
|
||||
delegationMixId: number
|
||||
delegationAmount: string
|
||||
delegationMixId: number;
|
||||
delegationAmount: string;
|
||||
}) => {
|
||||
try {
|
||||
const tx = await handleDelegate(delegationMixId, delegationAmount)
|
||||
return tx
|
||||
const tx = await handleDelegate(delegationMixId, delegationAmount);
|
||||
return tx;
|
||||
} catch (e) {
|
||||
console.error('Failed to delegate to mixnode', e)
|
||||
throw e
|
||||
console.error("Failed to delegate to mixnode", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (mixId && amount && onOk) {
|
||||
onOk({
|
||||
status: 'loading',
|
||||
})
|
||||
status: "loading",
|
||||
});
|
||||
try {
|
||||
if (!address) {
|
||||
throw new Error('Please connect your wallet')
|
||||
throw new Error("Please connect your wallet");
|
||||
}
|
||||
|
||||
const tx = await delegateToMixnode({
|
||||
delegationMixId: mixId,
|
||||
delegationAmount: amount.amount,
|
||||
})
|
||||
});
|
||||
|
||||
if (!tx) {
|
||||
throw new Error('Failed to delegate')
|
||||
throw new Error("Failed to delegate");
|
||||
}
|
||||
|
||||
onOk({
|
||||
status: 'success',
|
||||
message: 'Delegation can take up to one hour to process',
|
||||
status: "success",
|
||||
message: "Delegation can take up to one hour to process",
|
||||
transactions: [
|
||||
{
|
||||
url: `${urls('MAINNET').blockExplorer}/transaction/${
|
||||
url: `${urls("MAINNET").blockExplorer}/transaction/${
|
||||
tx.transactionHash
|
||||
}`,
|
||||
hash: tx.transactionHash,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to delegate', e)
|
||||
console.error("Failed to delegate", e);
|
||||
onOk({
|
||||
status: 'error',
|
||||
status: "error",
|
||||
message: (e as Error).message,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleAmountChanged = (newAmount: DecCoin) => {
|
||||
setAmount(newAmount)
|
||||
}
|
||||
setAmount(newAmount);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
validate()
|
||||
}, [amount, identityKey, mixId])
|
||||
validate();
|
||||
}, [amount, identityKey, mixId]);
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
@@ -170,7 +170,7 @@ export const DelegateModal = ({
|
||||
fullWidth
|
||||
autoFocus
|
||||
label="Amount"
|
||||
initialValue={amount?.amount || '10'}
|
||||
initialValue={amount?.amount || "10"}
|
||||
onChanged={handleAmountChanged}
|
||||
denom={denom}
|
||||
validationError={errorAmount}
|
||||
@@ -187,5 +187,5 @@ export const DelegateModal = ({
|
||||
|
||||
<ModalListItem label="Est. fee for this transaction will be calculated in your connected wallet" />
|
||||
</SimpleModal>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import React from 'react'
|
||||
import { Typography, SxProps, Stack } from '@mui/material'
|
||||
import { Link } from '@nymproject/react/link/Link'
|
||||
import { LoadingModal } from './LoadingModal'
|
||||
import { ConfirmationModal } from './ConfirmationModal'
|
||||
import { ErrorModal } from './ErrorModal'
|
||||
import React from "react";
|
||||
import { Typography, SxProps, Stack } from "@mui/material";
|
||||
import { Link } from "@nymproject/react";
|
||||
import { LoadingModal } from "./LoadingModal";
|
||||
import { ConfirmationModal } from "./ConfirmationModal";
|
||||
import { ErrorModal } from "./ErrorModal";
|
||||
|
||||
export type DelegationModalProps = {
|
||||
status: 'loading' | 'success' | 'error' | 'info'
|
||||
message?: string
|
||||
status: "loading" | "success" | "error" | "info";
|
||||
message?: string;
|
||||
transactions?: {
|
||||
url: string
|
||||
hash: string
|
||||
}[]
|
||||
}
|
||||
url: string;
|
||||
hash: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const DelegationModal: FCWithChildren<
|
||||
DelegationModalProps & {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
sx?: SxProps
|
||||
backdropProps?: object
|
||||
children?: React.ReactNode
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
> = ({
|
||||
status,
|
||||
@@ -32,18 +32,18 @@ export const DelegationModal: FCWithChildren<
|
||||
sx,
|
||||
backdropProps,
|
||||
}) => {
|
||||
if (status === 'loading')
|
||||
return <LoadingModal sx={sx} backdropProps={backdropProps} />
|
||||
if (status === "loading")
|
||||
return <LoadingModal sx={sx} backdropProps={backdropProps} />;
|
||||
|
||||
if (status === 'error') {
|
||||
if (status === "error") {
|
||||
return (
|
||||
<ErrorModal message={message} sx={sx} open={open} onClose={onClose}>
|
||||
{children}
|
||||
</ErrorModal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'info') {
|
||||
if (status === "info") {
|
||||
return (
|
||||
<ConfirmationModal
|
||||
open={open}
|
||||
@@ -53,7 +53,7 @@ export const DelegationModal: FCWithChildren<
|
||||
>
|
||||
<Typography>{message}</Typography>
|
||||
</ConfirmationModal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -91,5 +91,5 @@ export const DelegationModal: FCWithChildren<
|
||||
)}
|
||||
</Stack>
|
||||
</ConfirmationModal>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import * as React from "react";
|
||||
import {
|
||||
Link,
|
||||
Paper,
|
||||
@@ -9,49 +9,49 @@ import {
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCellProps,
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip'
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'
|
||||
import { Box } from '@mui/system'
|
||||
import { unymToNym } from '@/app/utils/currency'
|
||||
import { GatewayEnrichedRowType } from './Gateways/Gateways'
|
||||
import { MixnodeRowType } from './MixNodes'
|
||||
import { StakeSaturationProgressBar } from './MixNodes/Economics/StakeSaturationProgressBar'
|
||||
} from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Tooltip } from "@nymproject/react";
|
||||
import { CopyToClipboard } from "@nymproject/react";
|
||||
import { Box } from "@mui/system";
|
||||
import { unymToNym } from "@/app/utils/currency";
|
||||
import { GatewayEnrichedRowType } from "./Gateways/Gateways";
|
||||
import { MixnodeRowType } from "./MixNodes";
|
||||
import { StakeSaturationProgressBar } from "./MixNodes/Economics/StakeSaturationProgressBar";
|
||||
|
||||
export type ColumnsType = {
|
||||
field: string
|
||||
title: string
|
||||
headerAlign?: TableCellProps['align']
|
||||
width?: string | number
|
||||
tooltipInfo?: string
|
||||
}
|
||||
field: string;
|
||||
title: string;
|
||||
headerAlign?: TableCellProps["align"];
|
||||
width?: string | number;
|
||||
tooltipInfo?: string;
|
||||
};
|
||||
|
||||
export interface UniversalTableProps<T = any> {
|
||||
tableName: string
|
||||
columnsData: ColumnsType[]
|
||||
rows: T[]
|
||||
tableName: string;
|
||||
columnsData: ColumnsType[];
|
||||
rows: T[];
|
||||
}
|
||||
|
||||
function formatCellValues(val: string | number, field: string) {
|
||||
if (field === 'identity_key' && typeof val === 'string') {
|
||||
if (field === "identity_key" && typeof val === "string") {
|
||||
return (
|
||||
<Box display="flex" justifyContent="flex-end">
|
||||
<CopyToClipboard
|
||||
sx={{ mr: 1, mt: 0.5, fontSize: '18px' }}
|
||||
sx={{ mr: 1, mt: 0.5, fontSize: "18px" }}
|
||||
value={val}
|
||||
tooltip={`Copy identity key ${val} to clipboard`}
|
||||
/>
|
||||
<span>{val}</span>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (field === 'bond') {
|
||||
return unymToNym(val, 6)
|
||||
if (field === "bond") {
|
||||
return unymToNym(val, 6);
|
||||
}
|
||||
|
||||
if (field === 'owner') {
|
||||
if (field === "owner") {
|
||||
return (
|
||||
<Link
|
||||
underline="none"
|
||||
@@ -61,22 +61,22 @@ function formatCellValues(val: string | number, field: string) {
|
||||
>
|
||||
{val}
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (field === 'stake_saturation') {
|
||||
return <StakeSaturationProgressBar value={Number(val)} threshold={100} />
|
||||
if (field === "stake_saturation") {
|
||||
return <StakeSaturationProgressBar value={Number(val)} threshold={100} />;
|
||||
}
|
||||
|
||||
return val
|
||||
return val;
|
||||
}
|
||||
|
||||
export const DetailTable: FCWithChildren<{
|
||||
tableName: string
|
||||
columnsData: ColumnsType[]
|
||||
rows: MixnodeRowType[] | GatewayEnrichedRowType[]
|
||||
tableName: string;
|
||||
columnsData: ColumnsType[];
|
||||
rows: MixnodeRowType[] | GatewayEnrichedRowType[];
|
||||
}> = ({ tableName, columnsData, rows }: UniversalTableProps) => {
|
||||
const theme = useTheme()
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 1080 }} aria-label={tableName}>
|
||||
@@ -87,9 +87,9 @@ export const DetailTable: FCWithChildren<{
|
||||
key={field}
|
||||
sx={{ fontSize: 14, fontWeight: 600, width }}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
{tooltipInfo && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Tooltip
|
||||
title={tooltipInfo}
|
||||
id={field}
|
||||
@@ -115,7 +115,7 @@ export const DetailTable: FCWithChildren<{
|
||||
{rows.map((eachRow) => (
|
||||
<TableRow
|
||||
key={eachRow.id}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
>
|
||||
{columnsData?.map((data, index) => (
|
||||
<TableCell
|
||||
@@ -128,7 +128,7 @@ export const DetailTable: FCWithChildren<{
|
||||
width: 200,
|
||||
fontSize: 14,
|
||||
}}
|
||||
data-testid={`${data.title.replace(/ /g, '-')}-value`}
|
||||
data-testid={`${data.title.replace(/ /g, "-")}-value`}
|
||||
>
|
||||
{formatCellValues(
|
||||
eachRow[columnsData[index].field],
|
||||
@@ -141,5 +141,5 @@ export const DetailTable: FCWithChildren<{
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import React from 'react'
|
||||
import Box from '@mui/material/Box'
|
||||
import MuiLink from '@mui/material/Link'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { useIsMobile } from '../hooks/useIsMobile'
|
||||
import { NymVpnIcon } from '../icons/NymVpn'
|
||||
import { Socials } from './Socials'
|
||||
import Link from 'next/link'
|
||||
import React from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import MuiLink from "@mui/material/Link";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useIsMobile } from "../hooks/useIsMobile";
|
||||
import { NymVpnIcon } from "../icons/NymVpn";
|
||||
import { Socials } from "./Socials";
|
||||
import Link from "next/link";
|
||||
|
||||
export const Footer: FCWithChildren = () => {
|
||||
const isMobile = useIsMobile()
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
bgcolor: "background.default",
|
||||
mt: 3,
|
||||
pt: 3,
|
||||
pb: 3,
|
||||
@@ -25,11 +26,11 @@ export const Footer: FCWithChildren = () => {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: 'auto',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "auto",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
@@ -45,12 +46,12 @@ export const Footer: FCWithChildren = () => {
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 12,
|
||||
textAlign: isMobile ? 'center' : 'end',
|
||||
color: 'nym.muted.onDarkBg',
|
||||
textAlign: isMobile ? "center" : "end",
|
||||
color: "nym.muted.onDarkBg",
|
||||
}}
|
||||
>
|
||||
© {new Date().getFullYear()} Nym Technologies SA, all rights reserved
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import * as React from "react";
|
||||
import {
|
||||
Paper,
|
||||
Table,
|
||||
@@ -8,26 +8,26 @@ import {
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { Box } from '@mui/system'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip'
|
||||
import { EconomicsRowsType, EconomicsInfoRowWithIndex } from './types'
|
||||
import { UniversalTableProps } from '@/app/components/DetailTable'
|
||||
import { textColour } from '@/app/utils'
|
||||
} from "@mui/material";
|
||||
import { Box } from "@mui/system";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Tooltip } from "@nymproject/react";
|
||||
import { EconomicsRowsType, EconomicsInfoRowWithIndex } from "./types";
|
||||
import { UniversalTableProps } from "@/app/components/DetailTable";
|
||||
import { textColour } from "@/app/utils";
|
||||
|
||||
const formatCellValues = (value: EconomicsRowsType, field: string) => (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }} id="field">
|
||||
<Typography sx={{ mr: 1, fontWeight: '600', fontSize: '12px' }} id={field}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }} id="field">
|
||||
<Typography sx={{ mr: 1, fontWeight: "600", fontSize: "12px" }} id={field}>
|
||||
{value.value}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
|
||||
export const DelegatorsInfoTable: FCWithChildren<
|
||||
UniversalTableProps<EconomicsInfoRowWithIndex>
|
||||
> = ({ tableName, columnsData, rows }) => {
|
||||
const theme = useTheme()
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
@@ -39,7 +39,7 @@ export const DelegatorsInfoTable: FCWithChildren<
|
||||
key={field}
|
||||
sx={{ fontSize: 14, fontWeight: 600, width }}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
{tooltipInfo && (
|
||||
<Tooltip
|
||||
title={tooltipInfo}
|
||||
@@ -65,27 +65,27 @@ export const DelegatorsInfoTable: FCWithChildren<
|
||||
{rows?.map((eachRow) => (
|
||||
<TableRow
|
||||
key={eachRow.id}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
>
|
||||
{columnsData?.map((_, index: number) => {
|
||||
const { field } = columnsData[index]
|
||||
const value: EconomicsRowsType = (eachRow as any)[field]
|
||||
const { field } = columnsData[index];
|
||||
const value: EconomicsRowsType = (eachRow as any)[field];
|
||||
return (
|
||||
<TableCell
|
||||
key={_.title}
|
||||
sx={{
|
||||
color: textColour(value, field, theme),
|
||||
}}
|
||||
data-testid={`${_.title.replace(/ /g, '-')}-value`}
|
||||
data-testid={`${_.title.replace(/ /g, "-")}-value`}
|
||||
>
|
||||
{formatCellValues(value, columnsData[index].field)}
|
||||
</TableCell>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,93 +1,94 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import * as React from 'react'
|
||||
import { ExpandLess, ExpandMore, Menu } from '@mui/icons-material'
|
||||
import { CSSObject, styled, Theme, useTheme } from '@mui/material/styles'
|
||||
import { Link as MuiLink } from '@mui/material'
|
||||
import Button from '@mui/material/Button'
|
||||
import Box from '@mui/material/Box'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import MuiDrawer from '@mui/material/Drawer'
|
||||
import AppBar from '@mui/material/AppBar'
|
||||
import Toolbar from '@mui/material/Toolbar'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import List from '@mui/material/List'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import ListItemButton from '@mui/material/ListItemButton'
|
||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
||||
import ListItemText from '@mui/material/ListItemText'
|
||||
import { NYM_WEBSITE } from '@/app/api/constants'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { MobileDrawerClose } from '@/app/icons/MobileDrawerClose'
|
||||
import { NavOptionType, originalNavOptions } from '@/app/context/nav'
|
||||
import { DarkLightSwitchDesktop } from '@/app/components/Switch'
|
||||
import { Footer } from '@/app/components/Footer'
|
||||
import { ConnectKeplrWallet } from '@/app/components/Wallet/ConnectKeplrWallet'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import * as React from "react";
|
||||
import { Menu } from "@mui/icons-material";
|
||||
import { CSSObject, styled, Theme, useTheme } from "@mui/material/styles";
|
||||
import { Link as MuiLink } from "@mui/material";
|
||||
import Button from "@mui/material/Button";
|
||||
import Box from "@mui/material/Box";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import MuiDrawer from "@mui/material/Drawer";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import List from "@mui/material/List";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import { NYM_WEBSITE } from "@/app/api/constants";
|
||||
import { useMainContext } from "@/app/context/main";
|
||||
import { MobileDrawerClose } from "@/app/icons/MobileDrawerClose";
|
||||
import { NavOptionType, originalNavOptions } from "@/app/context/nav";
|
||||
import { DarkLightSwitchDesktop } from "@/app/components/Switch";
|
||||
import { Footer } from "@/app/components/Footer";
|
||||
import { ConnectKeplrWallet } from "@/app/components/Wallet/ConnectKeplrWallet";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { NymLogo } from "@nymproject/react";
|
||||
|
||||
const drawerWidth = 255
|
||||
const bannerHeight = 80
|
||||
const drawerWidth = 255;
|
||||
const bannerHeight = 80;
|
||||
|
||||
const openedMixin = (theme: Theme): CSSObject => ({
|
||||
width: drawerWidth,
|
||||
transition: theme.transitions.create('width', {
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
})
|
||||
overflowX: "hidden",
|
||||
});
|
||||
|
||||
const closedMixin = (theme: Theme): CSSObject => ({
|
||||
transition: theme.transitions.create('width', {
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
overflowX: "hidden",
|
||||
width: `calc(${theme.spacing(7)} + 1px)`,
|
||||
})
|
||||
});
|
||||
|
||||
const DrawerHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
const DrawerHeader = styled("div")(({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
padding: theme.spacing(0, 1),
|
||||
height: 64,
|
||||
}))
|
||||
}));
|
||||
|
||||
const Drawer = styled(MuiDrawer, {
|
||||
shouldForwardProp: (prop) => prop !== 'open',
|
||||
shouldForwardProp: (prop) => prop !== "open",
|
||||
})(({ theme, open }) => ({
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
boxSizing: 'border-box',
|
||||
whiteSpace: "nowrap",
|
||||
boxSizing: "border-box",
|
||||
...(open && {
|
||||
...openedMixin(theme),
|
||||
'& .MuiDrawer-paper': openedMixin(theme),
|
||||
"& .MuiDrawer-paper": openedMixin(theme),
|
||||
}),
|
||||
...(!open && {
|
||||
...closedMixin(theme),
|
||||
'& .MuiDrawer-paper': closedMixin(theme),
|
||||
"& .MuiDrawer-paper": closedMixin(theme),
|
||||
}),
|
||||
}))
|
||||
}));
|
||||
|
||||
type ExpandableButtonType = {
|
||||
title: string
|
||||
url: string
|
||||
isActive?: boolean
|
||||
Icon?: React.ReactNode
|
||||
nested?: NavOptionType[]
|
||||
isChild?: boolean
|
||||
isMobile: boolean
|
||||
drawIsTempOpen: boolean
|
||||
drawIsFixed: boolean
|
||||
isExternalLink?: boolean
|
||||
openDrawer: () => void
|
||||
closeDrawer?: () => void
|
||||
fixDrawerClose?: () => void
|
||||
}
|
||||
title: string;
|
||||
url: string;
|
||||
isActive?: boolean;
|
||||
Icon?: React.ReactNode;
|
||||
nested?: NavOptionType[];
|
||||
isChild?: boolean;
|
||||
isMobile: boolean;
|
||||
drawIsTempOpen: boolean;
|
||||
drawIsFixed: boolean;
|
||||
isExternalLink?: boolean;
|
||||
openDrawer: () => void;
|
||||
closeDrawer?: () => void;
|
||||
fixDrawerClose?: () => void;
|
||||
};
|
||||
|
||||
export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
|
||||
export const ExpandableButton = ({
|
||||
title,
|
||||
url,
|
||||
drawIsTempOpen,
|
||||
@@ -100,34 +101,34 @@ export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
fixDrawerClose,
|
||||
}) => {
|
||||
const { palette } = useTheme()
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
}: ExpandableButtonType) => {
|
||||
const { palette } = useTheme();
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
const handleClick = () => {
|
||||
if (title === 'Network Components') {
|
||||
return undefined
|
||||
if (title === "Network Components") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isExternalLink) {
|
||||
window.open(url, '_blank')
|
||||
window.open(url, "_blank");
|
||||
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!isExternalLink) {
|
||||
router.push(url, {})
|
||||
router.push(url, {});
|
||||
}
|
||||
|
||||
if (closeDrawer) {
|
||||
closeDrawer()
|
||||
closeDrawer();
|
||||
}
|
||||
}
|
||||
};
|
||||
const selectedStyle = {
|
||||
background: palette.nym.networkExplorer.nav.selected.main,
|
||||
borderRight: `3px solid ${palette.nym.highlight}`,
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -135,12 +136,12 @@ export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
|
||||
disablePadding
|
||||
disableGutters
|
||||
sx={{
|
||||
borderBottom: isChild ? 'none' : '1px solid rgba(255, 255, 255, 0.1)',
|
||||
borderBottom: isChild ? "none" : "1px solid rgba(255, 255, 255, 0.1)",
|
||||
...(pathname === url
|
||||
? selectedStyle
|
||||
: {
|
||||
background: palette.nym.networkExplorer.nav.background,
|
||||
borderRight: 'none',
|
||||
borderRight: "none",
|
||||
}),
|
||||
}}
|
||||
>
|
||||
@@ -151,10 +152,10 @@ export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
|
||||
pb: 2,
|
||||
background: isChild
|
||||
? palette.nym.networkExplorer.nav.selected.nested
|
||||
: 'none',
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: '39px' }}>{Icon}</ListItemIcon>
|
||||
<ListItemIcon sx={{ minWidth: "39px" }}>{Icon}</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={title}
|
||||
sx={{
|
||||
@@ -179,52 +180,52 @@ export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const Nav: FCWithChildren = ({ children }) => {
|
||||
const { environment } = useMainContext()
|
||||
const [drawerIsOpen, setDrawerToOpen] = React.useState(false)
|
||||
const [fixedOpen, setFixedOpen] = React.useState(false)
|
||||
export const Nav = ({ children }: { children: React.ReactNode }) => {
|
||||
const { environment } = useMainContext();
|
||||
const [drawerIsOpen, setDrawerToOpen] = React.useState(false);
|
||||
const [fixedOpen, setFixedOpen] = React.useState(false);
|
||||
// Set maintenance banner to false by default to don't display it
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(false)
|
||||
const theme = useTheme()
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(false);
|
||||
const theme = useTheme();
|
||||
|
||||
const explorerName = environment
|
||||
? `${environment} Explorer`
|
||||
: 'Mainnet Explorer'
|
||||
: "Mainnet Explorer";
|
||||
|
||||
const switchNetworkText =
|
||||
environment === 'mainnet' ? 'Switch to Testnet' : 'Switch to Mainnet'
|
||||
environment === "mainnet" ? "Switch to Testnet" : "Switch to Mainnet";
|
||||
const switchNetworkLink =
|
||||
environment === 'mainnet'
|
||||
? 'https://sandbox-explorer.nymtech.net'
|
||||
: 'https://explorer.nymtech.net'
|
||||
environment === "mainnet"
|
||||
? "https://sandbox-explorer.nymtech.net"
|
||||
: "https://explorer.nymtech.net";
|
||||
|
||||
const fixDrawerOpen = () => {
|
||||
setFixedOpen(true)
|
||||
setDrawerToOpen(true)
|
||||
}
|
||||
setFixedOpen(true);
|
||||
setDrawerToOpen(true);
|
||||
};
|
||||
|
||||
const fixDrawerClose = () => {
|
||||
setFixedOpen(false)
|
||||
setDrawerToOpen(false)
|
||||
}
|
||||
setFixedOpen(false);
|
||||
setDrawerToOpen(false);
|
||||
};
|
||||
|
||||
const tempDrawerOpen = () => {
|
||||
if (!fixedOpen) {
|
||||
setDrawerToOpen(true)
|
||||
setDrawerToOpen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tempDrawerClose = () => {
|
||||
if (!fixedOpen) {
|
||||
setDrawerToOpen(false)
|
||||
setDrawerToOpen(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppBar
|
||||
sx={{
|
||||
background: theme.palette.nym.networkExplorer.topNav.appBar,
|
||||
@@ -234,28 +235,28 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
<Toolbar
|
||||
disableGutters
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
ml: 0.5,
|
||||
}}
|
||||
>
|
||||
<IconButton component="a" href={NYM_WEBSITE} target="_blank">
|
||||
{/* <NymLogo /> */}
|
||||
<NymLogo width={25} />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
sx={{
|
||||
color: theme.palette.nym.networkExplorer.nav.text,
|
||||
fontSize: '18px',
|
||||
fontSize: "18px",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
@@ -274,7 +275,7 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
href={switchNetworkLink}
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
textTransform: 'none',
|
||||
textTransform: "none",
|
||||
width: 150,
|
||||
ml: 4,
|
||||
fontSize: 14,
|
||||
@@ -288,19 +289,19 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
<Box
|
||||
sx={{
|
||||
mr: 2,
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: 'auto',
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "auto",
|
||||
pr: 0,
|
||||
pl: 2,
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mr: 1 }}>
|
||||
@@ -324,10 +325,10 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
>
|
||||
<DrawerHeader
|
||||
sx={{
|
||||
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
justifyContent: 'flex-start',
|
||||
borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
|
||||
justifyContent: "flex-start",
|
||||
paddingLeft: 0,
|
||||
display: 'none',
|
||||
display: "none",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -363,11 +364,11 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
</Drawer>
|
||||
<Box
|
||||
style={{ width: `calc(100% - ${drawerWidth}px` }}
|
||||
sx={{ py: 5, px: 6, mt: 7 }}
|
||||
sx={{ py: 5, px: 6, mt: 7, bgcolor: "background.default" }}
|
||||
>
|
||||
{children}
|
||||
<Footer />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import * as React from 'react'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import * as React from "react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
@@ -12,61 +12,58 @@ import {
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
Toolbar,
|
||||
} from '@mui/material'
|
||||
import { Menu } from '@mui/icons-material'
|
||||
import { MaintenanceBanner } from '@nymproject/react/banners/MaintenanceBanner'
|
||||
import { useIsMobile } from '@/app/hooks/useIsMobile'
|
||||
import { MobileDrawerClose } from '@/app/icons/MobileDrawerClose'
|
||||
import { Footer } from '../Footer'
|
||||
import { ExpandableButton } from './DesktopNav'
|
||||
import { ConnectKeplrWallet } from '../Wallet/ConnectKeplrWallet'
|
||||
import { NetworkTitle } from '../NetworkTitle'
|
||||
import { originalNavOptions } from '@/app/context/nav'
|
||||
} from "@mui/material";
|
||||
import { Menu } from "@mui/icons-material";
|
||||
import { useIsMobile } from "@/app/hooks/useIsMobile";
|
||||
import { MobileDrawerClose } from "@/app/icons/MobileDrawerClose";
|
||||
import { Footer } from "../Footer";
|
||||
import { ExpandableButton } from "./DesktopNav";
|
||||
import { ConnectKeplrWallet } from "../Wallet/ConnectKeplrWallet";
|
||||
import { NetworkTitle } from "../NetworkTitle";
|
||||
import { originalNavOptions } from "@/app/context/nav";
|
||||
|
||||
export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
const theme = useTheme()
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false)
|
||||
export const MobileNav: FCWithChildren<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
||||
// Set maintenance banner to false by default to don't display it
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(false)
|
||||
const isSmallMobile = useIsMobile(400)
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(false);
|
||||
const isSmallMobile = useIsMobile(400);
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setDrawerOpen(!drawerOpen)
|
||||
}
|
||||
setDrawerOpen(!drawerOpen);
|
||||
};
|
||||
|
||||
const openDrawer = () => {
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
setDrawerOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<AppBar
|
||||
sx={{
|
||||
background: theme.palette.nym.networkExplorer.topNav.appBar,
|
||||
borderRadius: 0,
|
||||
}}
|
||||
>
|
||||
<MaintenanceBanner
|
||||
open={openMaintenance}
|
||||
onClick={() => setOpenMaintenance(false)}
|
||||
/>
|
||||
<Toolbar
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<IconButton onClick={toggleDrawer}>
|
||||
<Menu sx={{ color: 'primary.contrastText' }} />
|
||||
<Menu sx={{ color: "primary.contrastText" }} />
|
||||
</IconButton>
|
||||
{!isSmallMobile && <NetworkTitle />}
|
||||
</Box>
|
||||
@@ -91,7 +88,7 @@ export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
sx={{
|
||||
height: 64,
|
||||
background: theme.palette.nym.networkExplorer.nav.background,
|
||||
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
|
||||
}}
|
||||
>
|
||||
<ListItemButton
|
||||
@@ -100,8 +97,8 @@ export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
pt: 2,
|
||||
pb: 2,
|
||||
background: theme.palette.nym.networkExplorer.nav.background,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
@@ -126,11 +123,10 @@ export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
|
||||
<Box sx={{ width: '100%', p: 4, mt: 7 }}>
|
||||
<Box sx={{ width: "100%", p: 4, mt: 7, bgcolor: "background.default" }}>
|
||||
{children}
|
||||
<Footer />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import * as React from "react";
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
export const Title: FCWithChildren<{ text: string }> = ({ text }) => (
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
color: "text.primary",
|
||||
}}
|
||||
data-testid={text}
|
||||
>
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import * as React from 'react'
|
||||
import { scaleLinear } from 'd3-scale'
|
||||
import * as React from "react";
|
||||
import { scaleLinear } from "d3-scale";
|
||||
import {
|
||||
ComposableMap,
|
||||
Geographies,
|
||||
Geography,
|
||||
Marker,
|
||||
ZoomableGroup,
|
||||
} from 'react-simple-maps'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { ApiState, CountryDataResponse } from '../typeDefs/explorer-api'
|
||||
import MAP_TOPOJSON from '../assets/world-110m.json'
|
||||
} from "react-simple-maps";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { CircularProgress } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { ApiState, CountryDataResponse } from "../typeDefs/explorer-api";
|
||||
import MAP_TOPOJSON from "../assets/world-110m.json";
|
||||
|
||||
import "react-tooltip/dist/react-tooltip.css";
|
||||
|
||||
type MapProps = {
|
||||
userLocation?: [number, number]
|
||||
countryData?: ApiState<CountryDataResponse>
|
||||
loading: boolean
|
||||
}
|
||||
userLocation?: [number, number];
|
||||
countryData?: ApiState<CountryDataResponse>;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export const WorldMap: FCWithChildren<MapProps> = ({
|
||||
countryData,
|
||||
userLocation,
|
||||
loading,
|
||||
}) => {
|
||||
const { palette } = useTheme()
|
||||
const { palette } = useTheme();
|
||||
|
||||
// tooltip needs fixing
|
||||
|
||||
const colorScale = React.useMemo(() => {
|
||||
if (countryData?.data) {
|
||||
const heighestNumberOfNodes = Math.max(
|
||||
...Object.values(countryData.data).map((country) => country.nodes)
|
||||
)
|
||||
);
|
||||
return scaleLinear<string, string>()
|
||||
.domain([
|
||||
0,
|
||||
@@ -42,17 +46,17 @@ export const WorldMap: FCWithChildren<MapProps> = ({
|
||||
heighestNumberOfNodes,
|
||||
])
|
||||
.range(palette.nym.networkExplorer.map.fills)
|
||||
.unknown(palette.nym.networkExplorer.map.fills[0])
|
||||
.unknown(palette.nym.networkExplorer.map.fills[0]);
|
||||
}
|
||||
return () => palette.nym.networkExplorer.map.fills[0]
|
||||
}, [countryData, palette])
|
||||
return () => palette.nym.networkExplorer.map.fills[0];
|
||||
}, [countryData, palette]);
|
||||
|
||||
const [tooltipContent, setTooltipContent] = React.useState<string | null>(
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress />
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -61,8 +65,8 @@ export const WorldMap: FCWithChildren<MapProps> = ({
|
||||
data-tip=""
|
||||
style={{
|
||||
backgroundColor: palette.nym.networkExplorer.background.tertiary,
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
viewBox="0, 50, 800, 350"
|
||||
projection="geoMercator"
|
||||
@@ -75,7 +79,7 @@ export const WorldMap: FCWithChildren<MapProps> = ({
|
||||
<Geographies geography={MAP_TOPOJSON}>
|
||||
{({ geographies }) =>
|
||||
geographies.map((geo) => {
|
||||
const d = (countryData?.data || {})[geo.properties.ISO_A3]
|
||||
const d = (countryData?.data || {})[geo.properties.ISO_A3];
|
||||
return (
|
||||
<Geography
|
||||
key={geo.rsmKey}
|
||||
@@ -84,25 +88,25 @@ export const WorldMap: FCWithChildren<MapProps> = ({
|
||||
stroke={palette.nym.networkExplorer.map.stroke}
|
||||
strokeWidth={0.2}
|
||||
onMouseEnter={() => {
|
||||
const { NAME_LONG } = geo.properties
|
||||
const { NAME_LONG } = geo.properties;
|
||||
if (!userLocation) {
|
||||
setTooltipContent(`${NAME_LONG} | ${d?.nodes || 0}`)
|
||||
setTooltipContent(`${NAME_LONG} | ${d?.nodes || 0}`);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setTooltipContent('')
|
||||
setTooltipContent("");
|
||||
}}
|
||||
style={{
|
||||
hover:
|
||||
!userLocation && countryData
|
||||
? {
|
||||
fill: palette.nym.highlight,
|
||||
outline: 'white',
|
||||
outline: "white",
|
||||
}
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
</Geographies>
|
||||
@@ -123,7 +127,7 @@ export const WorldMap: FCWithChildren<MapProps> = ({
|
||||
)}
|
||||
</ZoomableGroup>
|
||||
</ComposableMap>
|
||||
<ReactTooltip>{tooltipContent}</ReactTooltip>
|
||||
<ReactTooltip id="tooltip">{tooltipContent}</ReactTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import * as React from 'react'
|
||||
import { PaletteMode } from '@mui/material'
|
||||
import * as React from "react";
|
||||
import { PaletteMode } from "@mui/material";
|
||||
import {
|
||||
ApiState,
|
||||
BlockResponse,
|
||||
@@ -13,110 +13,112 @@ import {
|
||||
ValidatorsResponse,
|
||||
Environment,
|
||||
DirectoryServiceProvider,
|
||||
} from '@/app/typeDefs/explorer-api'
|
||||
import { EnumFilterKey } from '@/app/typeDefs/filters'
|
||||
import { Api, getEnvironment } from '@/app/api'
|
||||
import { toPercentIntegerString } from '@/app/utils'
|
||||
import { NavOptionType, originalNavOptions } from './nav'
|
||||
} from "@/app/typeDefs/explorer-api";
|
||||
import { EnumFilterKey } from "@/app/typeDefs/filters";
|
||||
import { Api, getEnvironment } from "@/app/api";
|
||||
import { toPercentIntegerString } from "@/app/utils";
|
||||
import { NavOptionType, originalNavOptions } from "./nav";
|
||||
|
||||
interface StateData {
|
||||
summaryOverview?: ApiState<SummaryOverviewResponse>
|
||||
block?: ApiState<BlockResponse>
|
||||
countryData?: ApiState<CountryDataResponse>
|
||||
gateways?: ApiState<GatewayResponse>
|
||||
globalError?: string | undefined
|
||||
mixnodes?: ApiState<MixNodeResponse>
|
||||
mode: PaletteMode
|
||||
validators?: ApiState<ValidatorsResponse>
|
||||
environment?: Environment
|
||||
serviceProviders?: ApiState<DirectoryServiceProvider[]>
|
||||
summaryOverview?: ApiState<SummaryOverviewResponse>;
|
||||
block?: ApiState<BlockResponse>;
|
||||
countryData?: ApiState<CountryDataResponse>;
|
||||
gateways?: ApiState<GatewayResponse>;
|
||||
globalError?: string | undefined;
|
||||
mixnodes?: ApiState<MixNodeResponse>;
|
||||
mode: PaletteMode;
|
||||
validators?: ApiState<ValidatorsResponse>;
|
||||
environment?: Environment;
|
||||
serviceProviders?: ApiState<DirectoryServiceProvider[]>;
|
||||
}
|
||||
|
||||
interface StateApi {
|
||||
fetchMixnodes: (
|
||||
status?: MixnodeStatus
|
||||
) => Promise<MixNodeResponse | undefined>
|
||||
filterMixnodes: (filters: any, status: any) => void
|
||||
toggleMode: () => void
|
||||
) => Promise<MixNodeResponse | undefined>;
|
||||
filterMixnodes: (filters: any, status: any) => void;
|
||||
toggleMode: () => void;
|
||||
}
|
||||
|
||||
type State = StateData & StateApi
|
||||
type State = StateData & StateApi;
|
||||
|
||||
export const MainContext = React.createContext<State>({
|
||||
mode: 'dark',
|
||||
mode: "dark",
|
||||
toggleMode: () => undefined,
|
||||
filterMixnodes: () => null,
|
||||
fetchMixnodes: () => Promise.resolve(undefined),
|
||||
})
|
||||
});
|
||||
|
||||
export const useMainContext = (): React.ContextType<typeof MainContext> =>
|
||||
React.useContext<State>(MainContext)
|
||||
React.useContext<State>(MainContext);
|
||||
|
||||
export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
export const MainContextProvider: FCWithChildren<{
|
||||
children: React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
// network explorer environment
|
||||
const [environment, setEnvironment] = React.useState<Environment>()
|
||||
const [environment, setEnvironment] = React.useState<Environment>();
|
||||
|
||||
// light/dark mode
|
||||
const [mode, setMode] = React.useState<PaletteMode>('dark')
|
||||
const [mode, setMode] = React.useState<PaletteMode>("dark");
|
||||
|
||||
// global / banner error messaging
|
||||
const [globalError] = React.useState<string>()
|
||||
const [globalError] = React.useState<string>();
|
||||
|
||||
// various APIs for Overview page
|
||||
const [summaryOverview, setSummaryOverview] =
|
||||
React.useState<ApiState<SummaryOverviewResponse>>()
|
||||
const [mixnodes, setMixnodes] = React.useState<ApiState<MixNodeResponse>>()
|
||||
const [gateways, setGateways] = React.useState<ApiState<GatewayResponse>>()
|
||||
React.useState<ApiState<SummaryOverviewResponse>>();
|
||||
const [mixnodes, setMixnodes] = React.useState<ApiState<MixNodeResponse>>();
|
||||
const [gateways, setGateways] = React.useState<ApiState<GatewayResponse>>();
|
||||
const [validators, setValidators] =
|
||||
React.useState<ApiState<ValidatorsResponse>>()
|
||||
const [block, setBlock] = React.useState<ApiState<BlockResponse>>()
|
||||
React.useState<ApiState<ValidatorsResponse>>();
|
||||
const [block, setBlock] = React.useState<ApiState<BlockResponse>>();
|
||||
const [countryData, setCountryData] =
|
||||
React.useState<ApiState<CountryDataResponse>>()
|
||||
React.useState<ApiState<CountryDataResponse>>();
|
||||
const [serviceProviders, setServiceProviders] =
|
||||
React.useState<ApiState<DirectoryServiceProvider[]>>()
|
||||
React.useState<ApiState<DirectoryServiceProvider[]>>();
|
||||
|
||||
const toggleMode = () => setMode((m) => (m !== 'light' ? 'light' : 'dark'))
|
||||
const toggleMode = () => setMode((m) => (m !== "light" ? "light" : "dark"));
|
||||
|
||||
const fetchOverviewSummary = async () => {
|
||||
try {
|
||||
const data = await Api.fetchOverviewSummary()
|
||||
setSummaryOverview({ data, isLoading: false })
|
||||
const data = await Api.fetchOverviewSummary();
|
||||
setSummaryOverview({ data, isLoading: false });
|
||||
} catch (error) {
|
||||
setSummaryOverview({
|
||||
error:
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error('Overview summary api fail'),
|
||||
: new Error("Overview summary api fail"),
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMixnodes = async (status?: MixnodeStatus) => {
|
||||
let data
|
||||
setMixnodes((d) => ({ ...d, isLoading: true }))
|
||||
let data;
|
||||
setMixnodes((d) => ({ ...d, isLoading: true }));
|
||||
try {
|
||||
data = status
|
||||
? await Api.fetchMixnodesActiveSetByStatus(status)
|
||||
: await Api.fetchMixnodes()
|
||||
setMixnodes({ data, isLoading: false })
|
||||
: await Api.fetchMixnodes();
|
||||
setMixnodes({ data, isLoading: false });
|
||||
} catch (error) {
|
||||
setMixnodes({
|
||||
error: error instanceof Error ? error : new Error('Mixnode api fail'),
|
||||
error: error instanceof Error ? error : new Error("Mixnode api fail"),
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
return data
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const filterMixnodes = async (
|
||||
filters: { [key in EnumFilterKey]: number[] },
|
||||
status?: MixnodeStatus
|
||||
) => {
|
||||
setMixnodes((d) => ({ ...d, isLoading: true }))
|
||||
setMixnodes((d) => ({ ...d, isLoading: true }));
|
||||
const mxns = status
|
||||
? await Api.fetchMixnodesActiveSetByStatus(status)
|
||||
: await Api.fetchMixnodes()
|
||||
: await Api.fetchMixnodes();
|
||||
|
||||
const filtered = mxns?.filter(
|
||||
(m) =>
|
||||
@@ -126,101 +128,101 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
m.stake_saturation <= filters.stakeSaturation[1] &&
|
||||
m.avg_uptime >= filters.routingScore[0] &&
|
||||
m.avg_uptime <= filters.routingScore[1]
|
||||
)
|
||||
);
|
||||
|
||||
setMixnodes({ data: filtered, isLoading: false })
|
||||
}
|
||||
setMixnodes({ data: filtered, isLoading: false });
|
||||
};
|
||||
|
||||
const fetchGateways = async () => {
|
||||
setGateways((d) => ({ ...d, isLoading: true }))
|
||||
setGateways((d) => ({ ...d, isLoading: true }));
|
||||
try {
|
||||
const data = await Api.fetchGateways()
|
||||
setGateways({ data, isLoading: false })
|
||||
const data = await Api.fetchGateways();
|
||||
setGateways({ data, isLoading: false });
|
||||
} catch (error) {
|
||||
setGateways({
|
||||
error: error instanceof Error ? error : new Error('Gateways api fail'),
|
||||
error: error instanceof Error ? error : new Error("Gateways api fail"),
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const fetchValidators = async () => {
|
||||
try {
|
||||
const data = await Api.fetchValidators()
|
||||
setValidators({ data, isLoading: false })
|
||||
const data = await Api.fetchValidators();
|
||||
setValidators({ data, isLoading: false });
|
||||
} catch (error) {
|
||||
setValidators({
|
||||
error:
|
||||
error instanceof Error ? error : new Error('Validators api fail'),
|
||||
error instanceof Error ? error : new Error("Validators api fail"),
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const fetchBlock = async () => {
|
||||
try {
|
||||
const data = await Api.fetchBlock()
|
||||
setBlock({ data, isLoading: false })
|
||||
const data = await Api.fetchBlock();
|
||||
setBlock({ data, isLoading: false });
|
||||
} catch (error) {
|
||||
setBlock({
|
||||
error: error instanceof Error ? error : new Error('Block api fail'),
|
||||
error: error instanceof Error ? error : new Error("Block api fail"),
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const fetchCountryData = async () => {
|
||||
setCountryData({ data: undefined, isLoading: true })
|
||||
setCountryData({ data: undefined, isLoading: true });
|
||||
try {
|
||||
const res = await Api.fetchCountryData()
|
||||
setCountryData({ data: res, isLoading: false })
|
||||
const res = await Api.fetchCountryData();
|
||||
setCountryData({ data: res, isLoading: false });
|
||||
} catch (error) {
|
||||
setCountryData({
|
||||
error:
|
||||
error instanceof Error ? error : new Error('Country Data api fail'),
|
||||
error instanceof Error ? error : new Error("Country Data api fail"),
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchServiceProviders = async () => {
|
||||
setServiceProviders({ data: undefined, isLoading: true })
|
||||
setServiceProviders({ data: undefined, isLoading: true });
|
||||
try {
|
||||
const res = await Api.fetchServiceProviders()
|
||||
const res = await Api.fetchServiceProviders();
|
||||
const resWithRoutingScorePercentage = res.map((item) => ({
|
||||
...item,
|
||||
routing_score: item.routing_score
|
||||
? `${toPercentIntegerString(item.routing_score.toString())}%`
|
||||
: item.routing_score,
|
||||
}))
|
||||
}));
|
||||
setServiceProviders({
|
||||
data: resWithRoutingScorePercentage,
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
setServiceProviders({
|
||||
error:
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error('Service provider api fail'),
|
||||
: new Error("Service provider api fail"),
|
||||
isLoading: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (environment === 'mainnet') {
|
||||
fetchServiceProviders()
|
||||
if (environment === "mainnet") {
|
||||
fetchServiceProviders();
|
||||
}
|
||||
}, [environment])
|
||||
}, [environment]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setEnvironment(getEnvironment())
|
||||
setEnvironment(getEnvironment());
|
||||
Promise.all([
|
||||
fetchOverviewSummary(),
|
||||
fetchGateways(),
|
||||
fetchValidators(),
|
||||
fetchBlock(),
|
||||
fetchCountryData(),
|
||||
])
|
||||
}, [])
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const state = React.useMemo<State>(
|
||||
() => ({
|
||||
@@ -250,7 +252,7 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
validators,
|
||||
serviceProviders,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
return <MainContext.Provider value={state}>{children}</MainContext.Provider>
|
||||
}
|
||||
return <MainContext.Provider value={state}>{children}</MainContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react'
|
||||
import { FallbackProps } from 'react-error-boundary'
|
||||
import { Alert, AlertTitle, Container } from '@mui/material'
|
||||
import { NymThemeProvider } from '@nymproject/mui-theme'
|
||||
import { NymLogo } from '@nymproject/react/logo/NymLogo'
|
||||
import * as React from "react";
|
||||
import { FallbackProps } from "react-error-boundary";
|
||||
import { Alert, AlertTitle, Container } from "@mui/material";
|
||||
import { NymThemeProvider } from "@nymproject/mui-theme";
|
||||
import { NymLogo } from "@nymproject/react";
|
||||
|
||||
export const ErrorBoundaryContent: FCWithChildren<FallbackProps> = ({
|
||||
error,
|
||||
@@ -15,7 +15,7 @@ export const ErrorBoundaryContent: FCWithChildren<FallbackProps> = ({
|
||||
<AlertTitle>{error.name}</AlertTitle>
|
||||
{error.message}
|
||||
</Alert>
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
{process.env.NODE_ENV === "development" && (
|
||||
<Alert severity="info" sx={{ mt: 2 }} data-testid="stack-trace">
|
||||
<AlertTitle>Stack trace</AlertTitle>
|
||||
{error.stack}
|
||||
@@ -23,4 +23,4 @@ export const ErrorBoundaryContent: FCWithChildren<FallbackProps> = ({
|
||||
)}
|
||||
</Container>
|
||||
</NymThemeProvider>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { Metadata } from 'next'
|
||||
import '@interchain-ui/react/styles'
|
||||
import { App } from './App'
|
||||
import type { Metadata } from "next";
|
||||
import "@interchain-ui/react/styles";
|
||||
import { App } from "./App";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Nym Network Explorer',
|
||||
}
|
||||
title: "Nym Network Explorer",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<App>{children}</App>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,106 +1,107 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
import { Box, Card, Grid, Stack } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import React, { useMemo } from "react";
|
||||
import { Box, Card, Grid, Stack } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import {
|
||||
MRT_ColumnDef,
|
||||
MaterialReactTable,
|
||||
useMaterialReactTable,
|
||||
} from 'material-react-table'
|
||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'
|
||||
import { Tooltip as InfoTooltip } from '@nymproject/react/tooltip/Tooltip'
|
||||
import { diff, gte, rcompare } from 'semver'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { TableToolbar } from '@/app/components/TableToolbar'
|
||||
import { CustomColumnHeading } from '@/app/components/CustomColumnHeading'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import { unymToNym } from '@/app/utils/currency'
|
||||
import { Tooltip } from '@/app/components/Tooltip'
|
||||
import { NYM_BIG_DIPPER } from '@/app/api/constants'
|
||||
import { splice } from '@/app/utils'
|
||||
} from "material-react-table";
|
||||
import { GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { CopyToClipboard } from "@nymproject/react";
|
||||
import { Tooltip as InfoTooltip } from "@nymproject/react";
|
||||
import { diff, gte, rcompare } from "semver";
|
||||
import { useMainContext } from "@/app/context/main";
|
||||
import { TableToolbar } from "@/app/components/TableToolbar";
|
||||
import { CustomColumnHeading } from "@/app/components/CustomColumnHeading";
|
||||
import { Title } from "@/app/components/Title";
|
||||
import { unymToNym } from "@/app/utils/currency";
|
||||
import { Tooltip } from "@/app/components/Tooltip";
|
||||
import { NYM_BIG_DIPPER } from "@/app/api/constants";
|
||||
import { splice } from "@/app/utils";
|
||||
import {
|
||||
VersionDisplaySelector,
|
||||
VersionSelectOptions,
|
||||
} from '@/app/components/Gateways/VersionDisplaySelector'
|
||||
import StyledLink from '@/app/components/StyledLink'
|
||||
} from "@/app/components/Gateways/VersionDisplaySelector";
|
||||
import StyledLink from "@/app/components/StyledLink";
|
||||
import {
|
||||
GatewayRowType,
|
||||
gatewayToGridRow,
|
||||
} from '@/app/components/Gateways/Gateways'
|
||||
} from "@/app/components/Gateways/Gateways";
|
||||
import { InfoOutlined } from "@mui/icons-material";
|
||||
|
||||
const PageGateways = () => {
|
||||
const { gateways } = useMainContext()
|
||||
const { gateways } = useMainContext();
|
||||
const [versionFilter, setVersionFilter] =
|
||||
React.useState<VersionSelectOptions>(VersionSelectOptions.all)
|
||||
React.useState<VersionSelectOptions>(VersionSelectOptions.all);
|
||||
|
||||
const theme = useTheme()
|
||||
const theme = useTheme();
|
||||
|
||||
const highestVersion = React.useMemo(() => {
|
||||
if (gateways?.data) {
|
||||
const versions = gateways.data.reduce(
|
||||
(a: string[], b) => [...a, b.gateway.version],
|
||||
[]
|
||||
)
|
||||
const [lastestVersion] = versions.sort(rcompare)
|
||||
return lastestVersion
|
||||
);
|
||||
const [lastestVersion] = versions.sort(rcompare);
|
||||
return lastestVersion;
|
||||
}
|
||||
// fallback value
|
||||
return '2.0.0'
|
||||
}, [gateways])
|
||||
return "2.0.0";
|
||||
}, [gateways]);
|
||||
|
||||
const filterByLatestVersions = React.useMemo(() => {
|
||||
const filtered = gateways?.data?.filter((gw) => {
|
||||
const versionDiff = diff(highestVersion, gw.gateway.version)
|
||||
return versionDiff === 'patch' || versionDiff === null
|
||||
})
|
||||
if (filtered) return filtered
|
||||
return []
|
||||
}, [gateways])
|
||||
const versionDiff = diff(highestVersion, gw.gateway.version);
|
||||
return versionDiff === "patch" || versionDiff === null;
|
||||
});
|
||||
if (filtered) return filtered;
|
||||
return [];
|
||||
}, [gateways]);
|
||||
|
||||
const filterByOlderVersions = React.useMemo(() => {
|
||||
const filtered = gateways?.data?.filter((gw) => {
|
||||
const versionDiff = diff(highestVersion, gw.gateway.version)
|
||||
return versionDiff === 'major' || versionDiff === 'minor'
|
||||
})
|
||||
if (filtered) return filtered
|
||||
return []
|
||||
}, [gateways])
|
||||
const versionDiff = diff(highestVersion, gw.gateway.version);
|
||||
return versionDiff === "major" || versionDiff === "minor";
|
||||
});
|
||||
if (filtered) return filtered;
|
||||
return [];
|
||||
}, [gateways]);
|
||||
|
||||
const filteredByVersion = React.useMemo(() => {
|
||||
switch (versionFilter) {
|
||||
case VersionSelectOptions.latestVersion:
|
||||
return filterByLatestVersions
|
||||
return filterByLatestVersions;
|
||||
case VersionSelectOptions.olderVersions:
|
||||
return filterByOlderVersions
|
||||
return filterByOlderVersions;
|
||||
case VersionSelectOptions.all:
|
||||
return gateways?.data || []
|
||||
return gateways?.data || [];
|
||||
default:
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
}, [versionFilter, gateways])
|
||||
}, [versionFilter, gateways]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
return gatewayToGridRow(filteredByVersion || [])
|
||||
}, [filteredByVersion])
|
||||
return gatewayToGridRow(filteredByVersion || []);
|
||||
}, [filteredByVersion]);
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<GatewayRowType>[]>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'gateway-data',
|
||||
header: 'Gatewsay Data',
|
||||
id: "gateway-data",
|
||||
header: "Gatewsay Data",
|
||||
columns: [
|
||||
{
|
||||
id: 'identity_key',
|
||||
header: 'Identity Key',
|
||||
accessorKey: 'identity_key',
|
||||
id: "identity_key",
|
||||
header: "Identity Key",
|
||||
accessorKey: "identity_key",
|
||||
size: 250,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<CopyToClipboard
|
||||
sx={{ mr: 0.5, color: 'grey.400' }}
|
||||
sx={{ mr: 0.5, color: "grey.400" }}
|
||||
smallIcons
|
||||
value={row.original.identity_key}
|
||||
tooltip={`Copy identity key ${row.original.identity_key} to clipboard`}
|
||||
@@ -113,13 +114,13 @@ const PageGateways = () => {
|
||||
{splice(7, 29, row.original.identity_key)}
|
||||
</StyledLink>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node_performance',
|
||||
header: 'Node Performance',
|
||||
accessorKey: 'node_performance',
|
||||
id: "node_performance",
|
||||
header: "Node Performance",
|
||||
accessorKey: "node_performance",
|
||||
size: 200,
|
||||
Header: () => {
|
||||
return (
|
||||
@@ -134,10 +135,13 @@ const PageGateways = () => {
|
||||
}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
TooltipIcon={
|
||||
<InfoOutlined sx={{ width: 18, height: 18, mr: 1 }} />
|
||||
}
|
||||
/>
|
||||
<CustomColumnHeading headingTitle="Routing Score" />
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
},
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
@@ -148,13 +152,13 @@ const PageGateways = () => {
|
||||
>
|
||||
{`${row.original.node_performance}%`}
|
||||
</StyledLink>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
header: 'Version',
|
||||
accessorKey: 'version',
|
||||
id: "version",
|
||||
header: "Version",
|
||||
accessorKey: "version",
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
@@ -165,18 +169,18 @@ const PageGateways = () => {
|
||||
>
|
||||
{row.original.version}
|
||||
</StyledLink>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'location',
|
||||
header: 'Location',
|
||||
accessorKey: 'location',
|
||||
id: "location",
|
||||
header: "Location",
|
||||
accessorKey: "location",
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
sx={{ justifyContent: "flex-start", cursor: "pointer" }}
|
||||
data-testid="location-button"
|
||||
>
|
||||
<Tooltip
|
||||
@@ -185,22 +189,22 @@ const PageGateways = () => {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{row.original.location}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'host',
|
||||
header: 'IP:Port',
|
||||
accessorKey: 'host',
|
||||
id: "host",
|
||||
header: "IP:Port",
|
||||
accessorKey: "host",
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
@@ -211,13 +215,13 @@ const PageGateways = () => {
|
||||
>
|
||||
{row.original.host}
|
||||
</StyledLink>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'owner',
|
||||
header: 'Owner',
|
||||
accessorKey: 'owner',
|
||||
id: "owner",
|
||||
header: "Owner",
|
||||
accessorKey: "owner",
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
@@ -229,18 +233,18 @@ const PageGateways = () => {
|
||||
>
|
||||
{splice(7, 29, row.original.owner)}
|
||||
</StyledLink>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
];
|
||||
}, []);
|
||||
|
||||
const _columns: GridColDef[] = [
|
||||
{
|
||||
field: 'node_performance',
|
||||
align: 'center',
|
||||
field: "node_performance",
|
||||
align: "center",
|
||||
renderHeader: () => (
|
||||
<>
|
||||
<InfoTooltip
|
||||
@@ -251,14 +255,15 @@ const PageGateways = () => {
|
||||
bgColor={theme.palette.nym.networkExplorer.tooltip.background}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
TooltipIcon={<InfoOutlined sx={{ width: 18, height: 18, mr: 1 }} />}
|
||||
/>
|
||||
<CustomColumnHeading headingTitle="Routing Score" />
|
||||
</>
|
||||
),
|
||||
width: 120,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'center',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: "center",
|
||||
headerClassName: "MuiDataGrid-header-override",
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
@@ -269,13 +274,13 @@ const PageGateways = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
align: 'center',
|
||||
field: "version",
|
||||
align: "center",
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Version" />,
|
||||
width: 150,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'center',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: "center",
|
||||
headerClassName: "MuiDataGrid-header-override",
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
@@ -285,28 +290,28 @@ const PageGateways = () => {
|
||||
</StyledLink>
|
||||
),
|
||||
sortComparator: (a, b) => {
|
||||
if (gte(a, b)) return 1
|
||||
return -1
|
||||
if (gte(a, b)) return 1;
|
||||
return -1;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
field: "location",
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Location" />,
|
||||
width: 180,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: "left",
|
||||
headerClassName: "MuiDataGrid-header-override",
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Box
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
sx={{ justifyContent: "flex-start", cursor: "pointer" }}
|
||||
data-testid="location-button"
|
||||
>
|
||||
<Tooltip text={params.value} id="gateway-location-text">
|
||||
<Box
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{params.value}
|
||||
@@ -316,12 +321,12 @@ const PageGateways = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
field: "host",
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="IP:Port" />,
|
||||
width: 180,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: "left",
|
||||
headerClassName: "MuiDataGrid-header-override",
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
@@ -332,13 +337,13 @@ const PageGateways = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'owner',
|
||||
headerName: 'Owner',
|
||||
field: "owner",
|
||||
headerName: "Owner",
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
width: 180,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: "left",
|
||||
headerClassName: "MuiDataGrid-header-override",
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`${NYM_BIG_DIPPER}/account/${params.value}`}
|
||||
@@ -350,13 +355,13 @@ const PageGateways = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'bond',
|
||||
field: "bond",
|
||||
width: 150,
|
||||
disableColumnMenu: true,
|
||||
type: 'number',
|
||||
type: "number",
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Bond" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: 'left',
|
||||
headerClassName: "MuiDataGrid-header-override",
|
||||
headerAlign: "left",
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
@@ -366,12 +371,12 @@ const PageGateways = () => {
|
||||
</StyledLink>
|
||||
),
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data,
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -383,7 +388,7 @@ const PageGateways = () => {
|
||||
<Card
|
||||
sx={{
|
||||
padding: 2,
|
||||
height: '100%',
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<TableToolbar
|
||||
@@ -399,7 +404,7 @@ const PageGateways = () => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default PageGateways
|
||||
export default PageGateways;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
MaterialReactTable,
|
||||
useMaterialReactTable,
|
||||
type MRT_ColumnDef,
|
||||
} from 'material-react-table'
|
||||
import { Grid, Card, Button, Box, Stack } from '@mui/material'
|
||||
} from "material-react-table";
|
||||
import { Grid, Card, Button, Box, Stack } from "@mui/material";
|
||||
import {
|
||||
CustomColumnHeading,
|
||||
DelegateIconButton,
|
||||
@@ -21,81 +21,81 @@ import {
|
||||
Title,
|
||||
Tooltip,
|
||||
mixnodeToGridRow,
|
||||
} from '@/app/components'
|
||||
import { DelegationsProvider } from '@/app/context/delegations'
|
||||
import { useWalletContext } from '@/app/context/wallet'
|
||||
import { useGetMixNodeStatusColor, useIsMobile } from '@/app/hooks'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'
|
||||
import { splice } from '@/app/utils'
|
||||
import { currencyToString } from '@/app/utils/currency'
|
||||
import { NYM_BIG_DIPPER } from '@/app/api/constants'
|
||||
} from "@/app/components";
|
||||
import { DelegationsProvider } from "@/app/context/delegations";
|
||||
import { useWalletContext } from "@/app/context/wallet";
|
||||
import { useGetMixNodeStatusColor, useIsMobile } from "@/app/hooks";
|
||||
import { useMainContext } from "@/app/context/main";
|
||||
import { CopyToClipboard } from "@nymproject/react";
|
||||
import { splice } from "@/app/utils";
|
||||
import { currencyToString } from "@/app/utils/currency";
|
||||
import { NYM_BIG_DIPPER } from "@/app/api/constants";
|
||||
import {
|
||||
MixnodeStatusWithAll,
|
||||
toMixnodeStatus,
|
||||
} from '@/app/typeDefs/explorer-api'
|
||||
} from "@/app/typeDefs/explorer-api";
|
||||
|
||||
export default function MixnodesPage() {
|
||||
const isMobile = useIsMobile()
|
||||
const { isWalletConnected } = useWalletContext()
|
||||
const { mixnodes, fetchMixnodes } = useMainContext()
|
||||
const router = useRouter()
|
||||
const isMobile = useIsMobile();
|
||||
const { isWalletConnected } = useWalletContext();
|
||||
const { mixnodes, fetchMixnodes } = useMainContext();
|
||||
const router = useRouter();
|
||||
|
||||
const [itemSelectedForDelegation, setItemSelectedForDelegation] =
|
||||
React.useState<{
|
||||
mixId: number
|
||||
identityKey: string
|
||||
}>()
|
||||
mixId: number;
|
||||
identityKey: string;
|
||||
}>();
|
||||
const [confirmationModalProps, setConfirmationModalProps] = React.useState<
|
||||
DelegationModalProps | undefined
|
||||
>()
|
||||
>();
|
||||
|
||||
const search = useSearchParams()
|
||||
const status = search.get('status') as MixnodeStatusWithAll
|
||||
const search = useSearchParams();
|
||||
const status = search.get("status") as MixnodeStatusWithAll;
|
||||
|
||||
React.useEffect(() => {
|
||||
// when the status changes, get the mixnodes
|
||||
fetchMixnodes(toMixnodeStatus(status))
|
||||
}, [status])
|
||||
fetchMixnodes(toMixnodeStatus(status));
|
||||
}, [status]);
|
||||
|
||||
const handleMixnodeStatusChanged = (newStatus?: MixnodeStatusWithAll) => {
|
||||
router.push(
|
||||
newStatus && newStatus !== 'all'
|
||||
newStatus && newStatus !== "all"
|
||||
? `/network-components/mixnodes?status=${newStatus}`
|
||||
: '/network-components/mixnodes'
|
||||
)
|
||||
}
|
||||
: "/network-components/mixnodes"
|
||||
);
|
||||
};
|
||||
|
||||
const handleOnDelegate = useCallback(
|
||||
({ identityKey, mixId }: { identityKey: string; mixId: number }) => {
|
||||
if (!isWalletConnected) {
|
||||
setConfirmationModalProps({
|
||||
status: 'info',
|
||||
message: 'Please connect your wallet to delegate',
|
||||
})
|
||||
status: "info",
|
||||
message: "Please connect your wallet to delegate",
|
||||
});
|
||||
} else {
|
||||
setItemSelectedForDelegation({ identityKey, mixId })
|
||||
setItemSelectedForDelegation({ identityKey, mixId });
|
||||
}
|
||||
},
|
||||
[isWalletConnected]
|
||||
)
|
||||
);
|
||||
|
||||
const handleNewDelegation = (delegationModalProps: DelegationModalProps) => {
|
||||
setItemSelectedForDelegation(undefined)
|
||||
setConfirmationModalProps(delegationModalProps)
|
||||
}
|
||||
setItemSelectedForDelegation(undefined);
|
||||
setConfirmationModalProps(delegationModalProps);
|
||||
};
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<MixnodeRowType>[]>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'mixnode-data',
|
||||
header: 'Mixnode Data',
|
||||
id: "mixnode-data",
|
||||
header: "Mixnode Data",
|
||||
columns: [
|
||||
{
|
||||
id: 'delegate',
|
||||
accessorKey: 'delegate',
|
||||
id: "delegate",
|
||||
accessorKey: "delegate",
|
||||
size: isMobile ? 50 : 150,
|
||||
header: '',
|
||||
header: "",
|
||||
grow: false,
|
||||
Cell: ({ row }) => (
|
||||
<DelegateIconButton
|
||||
@@ -113,15 +113,15 @@ export default function MixnodesPage() {
|
||||
Filter: () => null,
|
||||
},
|
||||
{
|
||||
id: 'identity_key',
|
||||
header: 'Identity Key',
|
||||
accessorKey: 'identity_key',
|
||||
id: "identity_key",
|
||||
header: "Identity Key",
|
||||
accessorKey: "identity_key",
|
||||
size: 250,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<CopyToClipboard
|
||||
sx={{ mr: 0.5, color: 'grey.400' }}
|
||||
sx={{ mr: 0.5, color: "grey.400" }}
|
||||
smallIcons
|
||||
value={row.original.identity_key}
|
||||
tooltip={`Copy identity key ${row.original.identity_key} to clipboard`}
|
||||
@@ -134,13 +134,13 @@ export default function MixnodesPage() {
|
||||
{splice(7, 29, row.original.identity_key)}
|
||||
</StyledLink>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'bond',
|
||||
header: 'Stake',
|
||||
accessorKey: 'bond',
|
||||
id: "bond",
|
||||
header: "Stake",
|
||||
accessorKey: "bond",
|
||||
Cell: ({ row }) => (
|
||||
<StyledLink
|
||||
to={`/network-components/mixnodes/${row.original.mix_id}`}
|
||||
@@ -151,9 +151,9 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'stake_saturation',
|
||||
header: 'Stake Saturation',
|
||||
accessorKey: 'stake_saturation',
|
||||
id: "stake_saturation",
|
||||
header: "Stake Saturation",
|
||||
accessorKey: "stake_saturation",
|
||||
size: 225,
|
||||
Header() {
|
||||
return (
|
||||
@@ -161,7 +161,7 @@ export default function MixnodesPage() {
|
||||
headingTitle="Stake Saturation"
|
||||
tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is 940k NYMs, computed as S/K where S is target amount of tokens staked in the network and K is the number of nodes in the reward set."
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
Cell: ({ row }) => (
|
||||
<StyledLink
|
||||
@@ -171,9 +171,9 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'pledge_amount',
|
||||
header: 'Bond',
|
||||
accessorKey: 'pledge_amount',
|
||||
id: "pledge_amount",
|
||||
header: "Bond",
|
||||
accessorKey: "pledge_amount",
|
||||
size: 185,
|
||||
Header: () => (
|
||||
<CustomColumnHeading
|
||||
@@ -193,9 +193,9 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'profit_percentage',
|
||||
accessorKey: 'profit_percentage',
|
||||
header: 'Profit Margin',
|
||||
id: "profit_percentage",
|
||||
accessorKey: "profit_percentage",
|
||||
header: "Profit Margin",
|
||||
size: 145,
|
||||
Header: () => (
|
||||
<CustomColumnHeading
|
||||
@@ -211,10 +211,10 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'operating_cost',
|
||||
accessorKey: 'operating_cost',
|
||||
id: "operating_cost",
|
||||
accessorKey: "operating_cost",
|
||||
size: 220,
|
||||
header: 'Operating Cost',
|
||||
header: "Operating Cost",
|
||||
disableColumnMenu: true,
|
||||
Header: () => (
|
||||
<CustomColumnHeading
|
||||
@@ -230,10 +230,10 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'node_performance',
|
||||
accessorKey: 'node_performance',
|
||||
id: "node_performance",
|
||||
accessorKey: "node_performance",
|
||||
size: 200,
|
||||
header: 'Routing Score',
|
||||
header: "Routing Score",
|
||||
Header: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Routing Score"
|
||||
@@ -248,10 +248,10 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'owner',
|
||||
accessorKey: 'owner',
|
||||
id: "owner",
|
||||
accessorKey: "owner",
|
||||
size: 150,
|
||||
header: 'Owner',
|
||||
header: "Owner",
|
||||
Header: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
Cell: ({ row }) => (
|
||||
<StyledLink
|
||||
@@ -265,19 +265,19 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'location',
|
||||
accessorKey: 'location',
|
||||
header: 'Location',
|
||||
id: "location",
|
||||
accessorKey: "location",
|
||||
header: "Location",
|
||||
maxSize: 150,
|
||||
Header: () => <CustomColumnHeading headingTitle="Location" />,
|
||||
Cell: ({ row }) => (
|
||||
<Tooltip text={row.original.location} id="mixnode-location-text">
|
||||
<Box
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
cursor: 'pointer',
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
cursor: "pointer",
|
||||
color: useGetMixNodeStatusColor(row.original.status),
|
||||
}}
|
||||
>
|
||||
@@ -287,9 +287,9 @@ export default function MixnodesPage() {
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'host',
|
||||
accessorKey: 'host',
|
||||
header: 'Host',
|
||||
id: "host",
|
||||
accessorKey: "host",
|
||||
header: "Host",
|
||||
size: 130,
|
||||
Header: () => <CustomColumnHeading headingTitle="Host" />,
|
||||
Cell: ({ row }) => (
|
||||
@@ -303,12 +303,12 @@ export default function MixnodesPage() {
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [handleOnDelegate, isMobile])
|
||||
];
|
||||
}, [handleOnDelegate, isMobile]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
return mixnodeToGridRow(mixnodes?.data)
|
||||
}, [mixnodes?.data])
|
||||
return mixnodeToGridRow(mixnodes?.data);
|
||||
}, [mixnodes?.data]);
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
@@ -317,11 +317,11 @@ export default function MixnodesPage() {
|
||||
state: {
|
||||
isLoading: mixnodes?.isLoading,
|
||||
},
|
||||
layoutMode: 'grid-no-grow',
|
||||
layoutMode: "grid-no-grow",
|
||||
initialState: {
|
||||
columnPinning: { left: ['delegate'] },
|
||||
columnPinning: { left: ["delegate"] },
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<DelegationsProvider>
|
||||
@@ -333,7 +333,7 @@ export default function MixnodesPage() {
|
||||
<Card
|
||||
sx={{
|
||||
padding: 2,
|
||||
height: '100%',
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<TableToolbar
|
||||
@@ -350,7 +350,7 @@ export default function MixnodesPage() {
|
||||
fullWidth
|
||||
size="large"
|
||||
variant="outlined"
|
||||
onClick={() => router.push('/delegations')}
|
||||
onClick={() => router.push("/delegations")}
|
||||
>
|
||||
Delegations
|
||||
</Button>
|
||||
@@ -364,7 +364,7 @@ export default function MixnodesPage() {
|
||||
{itemSelectedForDelegation && (
|
||||
<DelegateModal
|
||||
onClose={() => {
|
||||
setItemSelectedForDelegation(undefined)
|
||||
setItemSelectedForDelegation(undefined);
|
||||
}}
|
||||
header="Delegate"
|
||||
buttonText="Delegate stake"
|
||||
@@ -382,19 +382,19 @@ export default function MixnodesPage() {
|
||||
{...confirmationModalProps}
|
||||
open={Boolean(confirmationModalProps)}
|
||||
onClose={async () => {
|
||||
setConfirmationModalProps(undefined)
|
||||
if (confirmationModalProps.status === 'success') {
|
||||
router.push('/delegations')
|
||||
setConfirmationModalProps(undefined);
|
||||
if (confirmationModalProps.status === "success") {
|
||||
router.push("/delegations");
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
width: {
|
||||
xs: '90%',
|
||||
xs: "90%",
|
||||
sm: 600,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DelegationsProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
import React, { useMemo } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -9,29 +9,28 @@ import {
|
||||
Grid,
|
||||
ListItem,
|
||||
Menu,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { TableToolbar } from '@/app/components/TableToolbar'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { CustomColumnHeading } from '@/app/components/CustomColumnHeading'
|
||||
} from "@mui/material";
|
||||
import { TableToolbar } from "@/app/components/TableToolbar";
|
||||
import { Title } from "@/app/components/Title";
|
||||
import { useMainContext } from "@/app/context/main";
|
||||
import { CustomColumnHeading } from "@/app/components/CustomColumnHeading";
|
||||
import {
|
||||
MRT_ColumnDef,
|
||||
MaterialReactTable,
|
||||
useMaterialReactTable,
|
||||
} from 'material-react-table'
|
||||
import { DirectoryServiceProvider } from '@/app/typeDefs/explorer-api'
|
||||
} from "material-react-table";
|
||||
import { DirectoryServiceProvider } from "@/app/typeDefs/explorer-api";
|
||||
|
||||
const SupportedApps = () => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
|
||||
const open = Boolean(anchorEl)
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
const anchorRef = React.useRef<HTMLButtonElement>(null)
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const anchorRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
return (
|
||||
<FormControl size="small">
|
||||
@@ -41,7 +40,7 @@ const SupportedApps = () => {
|
||||
size="large"
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
sx={{ mr: 2, textTransform: 'capitalize' }}
|
||||
sx={{ mr: 2, textTransform: "capitalize" }}
|
||||
>
|
||||
Supported Apps
|
||||
</Button>
|
||||
@@ -52,67 +51,67 @@ const SupportedApps = () => {
|
||||
<ListItem>Blockstream Green</ListItem>
|
||||
</Menu>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const ServiceProviders = () => {
|
||||
const { serviceProviders } = useMainContext()
|
||||
const { serviceProviders } = useMainContext();
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<DirectoryServiceProvider>[]>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'service-providers-data',
|
||||
header: 'Service Providers Data',
|
||||
id: "service-providers-data",
|
||||
header: "Service Providers Data",
|
||||
columns: [
|
||||
{
|
||||
id: 'address',
|
||||
accessorKey: 'address',
|
||||
header: 'Client ID',
|
||||
id: "address",
|
||||
accessorKey: "address",
|
||||
header: "Client ID",
|
||||
size: 450,
|
||||
},
|
||||
{
|
||||
id: 'service_type-type',
|
||||
accessorKey: 'service_type',
|
||||
header: 'Type',
|
||||
id: "service_type-type",
|
||||
accessorKey: "service_type",
|
||||
header: "Type",
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
id: 'routing_score-score',
|
||||
accessorKey: 'routing_score',
|
||||
header: 'Routing score',
|
||||
id: "routing_score-score",
|
||||
accessorKey: "routing_score",
|
||||
header: "Routing score",
|
||||
Header() {
|
||||
return (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Routing score"
|
||||
tooltipInfo="Routing score is only displayed for the service providers that had a successful ping within the last two hours"
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
Cell({ row }) {
|
||||
return row.original.routing_score || '-'
|
||||
return row.original.routing_score || "-";
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
];
|
||||
}, []);
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data: serviceProviders?.data || [],
|
||||
layoutMode: 'grid',
|
||||
layoutMode: "grid",
|
||||
state: {
|
||||
isLoading: serviceProviders?.isLoading,
|
||||
},
|
||||
initialState: {
|
||||
sorting: [
|
||||
{
|
||||
id: 'routing_score',
|
||||
id: "routing_score",
|
||||
desc: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -134,7 +133,7 @@ const ServiceProviders = () => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceProviders
|
||||
export default ServiceProviders;
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
SelectChangeEvent,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { ContentCard } from '@/app/components/ContentCard'
|
||||
import { TableToolbar } from '@/app/components/TableToolbar'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import CosmosKitProvider from '@/app/context/cosmos-kit'
|
||||
import { WalletProvider } from '@/app/context/wallet'
|
||||
import { NetworkExplorerThemeProvider } from '@/app/theme'
|
||||
import { MainContextProvider } from '@/app/context/main'
|
||||
import React from "react";
|
||||
import CosmosKitProvider from "@/app/context/cosmos-kit";
|
||||
import { WalletProvider } from "@/app/context/wallet";
|
||||
import { NetworkExplorerThemeProvider } from "@/app/theme";
|
||||
import { MainContextProvider } from "@/app/context/main";
|
||||
|
||||
const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
@@ -13,7 +13,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
</CosmosKitProvider>
|
||||
</NetworkExplorerThemeProvider>
|
||||
</MainContextProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export { Providers }
|
||||
export { Providers };
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import * as React from 'react'
|
||||
import { NymNetworkExplorerThemeProvider } from '@nymproject/mui-theme'
|
||||
import { useMainContext } from '../context/main'
|
||||
import * as React from "react";
|
||||
import { NymNetworkExplorerThemeProvider } from "@nymproject/mui-theme";
|
||||
import { useMainContext } from "../context/main";
|
||||
|
||||
export const NetworkExplorerThemeProvider: FCWithChildren = ({ children }) => {
|
||||
const { mode } = useMainContext()
|
||||
export const NetworkExplorerThemeProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { mode } = useMainContext();
|
||||
|
||||
return (
|
||||
<NymNetworkExplorerThemeProvider mode={mode}>
|
||||
{children}
|
||||
</NymNetworkExplorerThemeProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
declare type FCWithChildren<P = {}> = React.FC<React.PropsWithChildren<P>>;
|
||||
declare type FCWithChildren<P = object> = React.FC<React.PropsWithChildren<P>>;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
declare module 'react-tooltip';
|
||||
@@ -1,19 +1,21 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { MutableRefObject } from 'react';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { registerLocale, getName } from 'i18n-iso-countries';
|
||||
import Big from 'big.js';
|
||||
import { CountryData } from '@/app/typeDefs/explorer-api';
|
||||
import { EconomicsRowsType } from '@/app/components/MixNodes/Economics/types';
|
||||
import { Network } from '../typeDefs/network';
|
||||
import { MutableRefObject } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { registerLocale, getName } from "i18n-iso-countries";
|
||||
import Big from "big.js";
|
||||
import { CountryData } from "@/app/typeDefs/explorer-api";
|
||||
import { EconomicsRowsType } from "@/app/components/MixNodes/Economics/types";
|
||||
import { Network } from "../typeDefs/network";
|
||||
|
||||
registerLocale(require('i18n-iso-countries/langs/en.json'));
|
||||
registerLocale(require("i18n-iso-countries/langs/en.json"));
|
||||
|
||||
export function formatNumber(num: number): string {
|
||||
return new Intl.NumberFormat().format(num);
|
||||
}
|
||||
|
||||
export function scrollToRef(ref: MutableRefObject<HTMLDivElement | undefined>): void {
|
||||
export function scrollToRef(
|
||||
ref: MutableRefObject<HTMLDivElement | undefined>
|
||||
): void {
|
||||
if (ref?.current) ref.current.scrollIntoView();
|
||||
}
|
||||
|
||||
@@ -25,13 +27,15 @@ export type CountryDataRowType = {
|
||||
percentage: string;
|
||||
};
|
||||
|
||||
export function countryDataToGridRow(countriesData: CountryData[]): CountryDataRowType[] {
|
||||
export function countryDataToGridRow(
|
||||
countriesData: CountryData[]
|
||||
): CountryDataRowType[] {
|
||||
const totalNodes = countriesData.reduce((acc, obj) => acc + obj.nodes, 0);
|
||||
const formatted = countriesData.map((each: CountryData, index: number) => {
|
||||
const updatedCountryRecord: CountryDataRowType = {
|
||||
...each,
|
||||
id: index,
|
||||
countryName: getName(each.ISO3, 'en', { select: 'alias' }),
|
||||
countryName: getName(each.ISO3, "en", { select: "alias" }) || "",
|
||||
percentage: ((each.nodes * 100) / totalNodes).toFixed(1),
|
||||
};
|
||||
return updatedCountryRecord;
|
||||
@@ -41,16 +45,21 @@ export function countryDataToGridRow(countriesData: CountryData[]): CountryDataR
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export const splice = (start: number, deleteCount: number, address?: string): string => {
|
||||
export const splice = (
|
||||
start: number,
|
||||
deleteCount: number,
|
||||
address?: string
|
||||
): string => {
|
||||
if (address) {
|
||||
const array = address.split('');
|
||||
array.splice(start, deleteCount, '...');
|
||||
return array.join('');
|
||||
const array = address.split("");
|
||||
array.splice(start, deleteCount, "...");
|
||||
return array.join("");
|
||||
}
|
||||
return '';
|
||||
return "";
|
||||
};
|
||||
|
||||
export const trimAddress = (address = '', trimBy = 6) => `${address.slice(0, trimBy)}...${address.slice(-trimBy)}`;
|
||||
export const trimAddress = (address = "", trimBy = 6) =>
|
||||
`${address.slice(0, trimBy)}...${address.slice(-trimBy)}`;
|
||||
|
||||
/**
|
||||
* Converts a stringified percentage float (0.0-1.0) to a stringified integer (0-100).
|
||||
@@ -58,27 +67,33 @@ export const trimAddress = (address = '', trimBy = 6) => `${address.slice(0, tri
|
||||
* @param value - the percentage to convert
|
||||
* @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 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) => {
|
||||
export const textColour = (
|
||||
value: EconomicsRowsType,
|
||||
field: string,
|
||||
theme: Theme
|
||||
) => {
|
||||
const progressBarValue = value?.progressBarValue || 0;
|
||||
const fieldValue = value.value;
|
||||
|
||||
if (progressBarValue > 100) {
|
||||
return theme.palette.warning.main;
|
||||
}
|
||||
if (field === 'selectionChance') {
|
||||
if (field === "selectionChance") {
|
||||
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
|
||||
switch (fieldValue) {
|
||||
case 'High':
|
||||
case 'VeryHigh':
|
||||
case "High":
|
||||
case "VeryHigh":
|
||||
return theme.palette.nym.networkExplorer.selectionChance.overModerate;
|
||||
case 'Good':
|
||||
case 'Moderate':
|
||||
case "Good":
|
||||
case "Moderate":
|
||||
return theme.palette.nym.networkExplorer.selectionChance.moderate;
|
||||
case 'Low':
|
||||
case 'VeryLow':
|
||||
case "Low":
|
||||
case "VeryLow":
|
||||
return theme.palette.nym.networkExplorer.selectionChance.underModerate;
|
||||
default:
|
||||
return theme.palette.nym.wallet.fee;
|
||||
@@ -101,8 +116,12 @@ export const isLessThan = (a: number, b: number) => a < b;
|
||||
*
|
||||
*/
|
||||
|
||||
export const isBalanceEnough = (fee: string, tx: string = '0', balance: string = '0') => {
|
||||
console.log('balance', balance, fee, tx);
|
||||
export const isBalanceEnough = (
|
||||
fee: string,
|
||||
tx: string = "0",
|
||||
balance: string = "0"
|
||||
) => {
|
||||
console.log("balance", balance, fee, tx);
|
||||
try {
|
||||
return Big(balance).gte(Big(fee).plus(Big(tx)));
|
||||
} catch (e) {
|
||||
@@ -112,13 +131,13 @@ export const isBalanceEnough = (fee: string, tx: string = '0', balance: string =
|
||||
};
|
||||
|
||||
export const urls = (networkName?: Network) =>
|
||||
networkName === 'MAINNET'
|
||||
networkName === "MAINNET"
|
||||
? {
|
||||
mixnetExplorer: 'https://mixnet.explorers.guru/',
|
||||
blockExplorer: 'https://blocks.nymtech.net',
|
||||
networkExplorer: 'https://explorer.nymtech.net',
|
||||
}
|
||||
mixnetExplorer: "https://mixnet.explorers.guru/",
|
||||
blockExplorer: "https://blocks.nymtech.net",
|
||||
networkExplorer: "https://explorer.nymtech.net",
|
||||
}
|
||||
: {
|
||||
blockExplorer: `https://${networkName}-blocks.nymtech.net`,
|
||||
networkExplorer: `https://${networkName}-explorer.nymtech.net`,
|
||||
};
|
||||
blockExplorer: `https://${networkName}-blocks.nymtech.net`,
|
||||
networkExplorer: `https://${networkName}-explorer.nymtech.net`,
|
||||
};
|
||||
|
||||
@@ -4,25 +4,56 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"build:prod": "yarn --cwd .. build && next build",
|
||||
"build:prod": "pnpm i && pnpm -C .. build && next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/nym-validator-client": "0.18.0",
|
||||
"@chain-registry/types": "^0.45.9",
|
||||
"@cosmjs/cosmwasm-stargate": "^0.29.5",
|
||||
"@cosmos-kit/core": "^2.13.1",
|
||||
"@cosmos-kit/keplr-extension": "^2.12.2",
|
||||
"@cosmos-kit/react": "^2.17.2",
|
||||
"@mui/base": "5.0.0-beta.40",
|
||||
"@mui/icons-material": "^5.2.0",
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@mui/system": "^5.15.20",
|
||||
"@mui/x-data-grid": "7.1.1",
|
||||
"@mui/x-date-pickers": "7.1.1",
|
||||
"@nymproject/contract-clients": "1.2.4-rc.1",
|
||||
"@nymproject/mui-theme": "workspace:^1.0.0",
|
||||
"@nymproject/nym-validator-client": "^0.18.0",
|
||||
"@nymproject/react": "workspace:^1.0.0",
|
||||
"@nymproject/types": "workspace:^1.0.0",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"big.js": "^6.2.1",
|
||||
"chain-registry": "^1.63.12",
|
||||
"d3-scale": "^4.0.2",
|
||||
"date-fns": "^2.28.0",
|
||||
"i18n-iso-countries": "^7.11.2",
|
||||
"lodash": "^4.17.21",
|
||||
"material-react-table": "^2.12.1",
|
||||
"next": "14.1.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"material-react-table": "^2.12.1",
|
||||
"@mui/x-date-pickers": "7.1.1",
|
||||
"@mui/x-data-grid": "7.1.1"
|
||||
"react-google-charts": "^4.0.1",
|
||||
"react-identicons": "^1.2.5",
|
||||
"react-simple-maps": "^3.0.0",
|
||||
"react-tooltip": "^5.27.0",
|
||||
"semver": "^6.3.1",
|
||||
"use-clipboard-copy": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/big.js": "^6.1.6",
|
||||
"@types/lodash": "^4.17.6",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-simple-maps": "^3.0.4",
|
||||
"@types/semver": "^7.3.8",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.4",
|
||||
"typescript": "^5"
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
"@mui/system": "^5.0.1",
|
||||
"@mui/x-data-grid": "^5.0.0-beta.5",
|
||||
"@nymproject/contract-clients": "1.2.4-rc.1",
|
||||
"@nymproject/mui-theme": "^1.0.0",
|
||||
"@nymproject/mui-theme": "workspace: ^1.0.0",
|
||||
"@nymproject/node-tester": "^1.0.0",
|
||||
"@nymproject/nym-validator-client": "^0.18.0",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/types": "^1.0.0",
|
||||
"@nymproject/react": "workspace:^1.0.0",
|
||||
"@nymproject/types": "workspace:^1.0.0",
|
||||
"@tauri-apps/api": "^1.5.1",
|
||||
"big.js": "^6.2.1",
|
||||
"bs58": "^5.0.0",
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@nymproject/eslint-config-react-typescript": "workspace:^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/addon-actions": "^6.5.8",
|
||||
"@storybook/addon-essentials": "^6.5.8",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::commands::upgrade_helpers;
|
||||
use log::{error, info};
|
||||
use nym_config::{save_formatted_config_to_file, OptionalSet};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
@@ -121,6 +122,8 @@ pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), Ga
|
||||
}
|
||||
|
||||
pub(crate) fn try_load_current_config(id: &str) -> Result<Config, GatewayError> {
|
||||
upgrade_helpers::try_upgrade_config(id)?;
|
||||
|
||||
Config::read_from_default_path(id).map_err(|err| {
|
||||
error!(
|
||||
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::Cli;
|
||||
use anyhow::bail;
|
||||
use clap::CommandFactory;
|
||||
use clap::Subcommand;
|
||||
use log::{error, warn};
|
||||
use log::warn;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use std::io::IsTerminal;
|
||||
use std::time::Duration;
|
||||
@@ -18,6 +17,7 @@ pub(crate) mod run;
|
||||
pub(crate) mod setup_ip_packet_router;
|
||||
pub(crate) mod setup_network_requester;
|
||||
pub(crate) mod sign;
|
||||
mod upgrade_helpers;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
@@ -55,12 +55,6 @@ pub(crate) enum Commands {
|
||||
pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> {
|
||||
let bin_name = "nym-gateway";
|
||||
|
||||
if !args.force_run {
|
||||
let msg = "standalone gateways have been deprecated - please migrate to a `nym-node` via `nym-node migrate gateways` command";
|
||||
error!("{msg}");
|
||||
bail!("{msg}")
|
||||
}
|
||||
|
||||
warn!("standalone gateways have been deprecated - please consider migrating it to a `nym-node` via `nym-node migrate gateway` command");
|
||||
if std::io::stdout().is_terminal() {
|
||||
// if user is running it in terminal session,
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use log::info;
|
||||
use nym_gateway::config::old_config_v1_1_20::ConfigV1_1_20;
|
||||
use nym_gateway::config::old_config_v1_1_28::ConfigV1_1_28;
|
||||
use nym_gateway::config::old_config_v1_1_29::ConfigV1_1_29;
|
||||
use nym_gateway::config::old_config_v1_1_31::ConfigV1_1_31;
|
||||
use nym_gateway::config::old_config_v1_1_36::ConfigV1_1_36;
|
||||
use nym_gateway::config::{default_config_filepath, Config};
|
||||
use nym_gateway::error::GatewayError;
|
||||
|
||||
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, GatewayError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.20 (which is incompatible with the current, i.e. 1.1.21+)
|
||||
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.20 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_28 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_29 = updated_step1.into();
|
||||
let updated_step3: ConfigV1_1_31 = updated_step2.into();
|
||||
let updated_step4: ConfigV1_1_36 = updated_step3.into();
|
||||
let updated: Config = updated_step4.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_28_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.28 (which is incompatible with the current, i.e. 1.1.29+)
|
||||
let Ok(old_config) = ConfigV1_1_28::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.28 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_29 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_31 = updated_step1.into();
|
||||
let updated_step3: ConfigV1_1_36 = updated_step2.into();
|
||||
let updated: Config = updated_step3.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_29_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.29 (which is incompatible with the current, i.e. 1.1.30+)
|
||||
let Ok(old_config) = ConfigV1_1_29::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.29 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_31 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_36 = updated_step1.into();
|
||||
let updated: Config = updated_step2.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_31_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.31 (which is incompatible with the current, i.e. 1.1.32+)
|
||||
let Ok(old_config) = ConfigV1_1_31::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.31 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_36 = old_config.into();
|
||||
let updated: Config = updated_step1.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_36_config(id: &str) -> Result<bool, GatewayError> {
|
||||
// explicitly load it as v1.1.36 (which is incompatible with the current, i.e. 1.1.37+)
|
||||
let Ok(old_config) = ConfigV1_1_36::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the gateway is using <= v1.1.36 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| GatewayError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn try_upgrade_config(id: &str) -> Result<(), GatewayError> {
|
||||
if try_upgrade_v1_1_20_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_28_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_29_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_31_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_36_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -22,6 +22,11 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub use crate::config::persistence::paths::GatewayPaths;
|
||||
|
||||
pub mod old_config_v1_1_20;
|
||||
pub mod old_config_v1_1_28;
|
||||
pub mod old_config_v1_1_29;
|
||||
pub mod old_config_v1_1_31;
|
||||
pub mod old_config_v1_1_36;
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::old_config_v1_1_28::{
|
||||
ConfigV1_1_28, DebugV1_1_28, GatewayPathsV1_1_28, GatewayV1_1_28, KeysPathsV1_1_28,
|
||||
LoggingSettingsV1_1_28,
|
||||
};
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_validator_client::nyxd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
const DEFAULT_MIX_LISTENING_PORT: u16 = 1789;
|
||||
const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000;
|
||||
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// returns a `0.0.0.0` / INADDR_ANY
|
||||
fn bind_all_address() -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
pub struct ConfigV1_1_20 {
|
||||
gateway: GatewayV1_1_20,
|
||||
|
||||
#[serde(default)]
|
||||
logging: LoggingV1_1_20,
|
||||
#[serde(default)]
|
||||
debug: DebugV1_1_20,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_20> for ConfigV1_1_28 {
|
||||
fn from(value: ConfigV1_1_20) -> Self {
|
||||
ConfigV1_1_28 {
|
||||
gateway: GatewayV1_1_28 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
storage_paths: GatewayPathsV1_1_28 {
|
||||
keys: KeysPathsV1_1_28 {
|
||||
private_identity_key_file: value.gateway.private_identity_key_file,
|
||||
public_identity_key_file: value.gateway.public_identity_key_file,
|
||||
private_sphinx_key_file: value.gateway.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.gateway.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.gateway.persistent_storage,
|
||||
},
|
||||
logging: value.logging.into(),
|
||||
debug: value.debug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_20 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
// unless this is run on some esoteric system, it should not fail thus the expect is fine
|
||||
#[allow(clippy::expect_used)]
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("gateways")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_20 {
|
||||
version: String,
|
||||
id: String,
|
||||
|
||||
#[serde(default)]
|
||||
only_coconut_credentials: bool,
|
||||
#[serde(default = "bind_all_address")]
|
||||
listening_address: IpAddr,
|
||||
announce_address: String,
|
||||
mix_port: u16,
|
||||
clients_port: u16,
|
||||
private_identity_key_file: PathBuf,
|
||||
public_identity_key_file: PathBuf,
|
||||
private_sphinx_key_file: PathBuf,
|
||||
public_sphinx_key_file: PathBuf,
|
||||
enabled_statistics: bool,
|
||||
statistics_service_url: Url,
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
nym_api_urls: Vec<Url>,
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
nyxd_urls: Vec<Url>,
|
||||
cosmos_mnemonic: bip39::Mnemonic,
|
||||
nym_root_directory: PathBuf,
|
||||
persistent_storage: PathBuf,
|
||||
wallet_address: Option<nyxd::AccountId>,
|
||||
}
|
||||
|
||||
impl Default for GatewayV1_1_20 {
|
||||
fn default() -> Self {
|
||||
// allow usage of `expect` here as our default mainnet values should have been well-formed.
|
||||
#[allow(clippy::expect_used)]
|
||||
GatewayV1_1_20 {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
only_coconut_credentials: false,
|
||||
listening_address: bind_all_address(),
|
||||
announce_address: "127.0.0.1".to_string(),
|
||||
mix_port: DEFAULT_MIX_LISTENING_PORT,
|
||||
clients_port: DEFAULT_CLIENT_LISTENING_PORT,
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
private_sphinx_key_file: Default::default(),
|
||||
public_sphinx_key_file: Default::default(),
|
||||
enabled_statistics: false,
|
||||
statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS)
|
||||
.expect("Invalid default statistics service URL"),
|
||||
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
|
||||
nyxd_urls: vec![Url::from_str(NYXD_URL).expect("Invalid default nyxd URL")],
|
||||
cosmos_mnemonic: bip39::Mnemonic::generate(24)
|
||||
.expect("failed to generate fresh mnemonic"),
|
||||
nym_root_directory: ConfigV1_1_20::default_root_directory(),
|
||||
persistent_storage: Default::default(),
|
||||
wallet_address: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct LoggingV1_1_20 {}
|
||||
|
||||
impl From<LoggingV1_1_20> for LoggingSettingsV1_1_28 {
|
||||
fn from(_value: LoggingV1_1_20) -> Self {
|
||||
LoggingSettingsV1_1_28 {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
struct DebugV1_1_20 {
|
||||
#[serde(with = "humantime_serde")]
|
||||
packet_forwarding_initial_backoff: Duration,
|
||||
#[serde(with = "humantime_serde")]
|
||||
packet_forwarding_maximum_backoff: Duration,
|
||||
#[serde(with = "humantime_serde")]
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
#[serde(with = "humantime_serde")]
|
||||
presence_sending_delay: Duration,
|
||||
stored_messages_filename_length: u16,
|
||||
message_retrieval_limit: i64,
|
||||
use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl From<DebugV1_1_20> for DebugV1_1_28 {
|
||||
fn from(value: DebugV1_1_20) -> Self {
|
||||
DebugV1_1_28 {
|
||||
packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.presence_sending_delay,
|
||||
stored_messages_filename_length: value.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.use_legacy_framed_packet_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_20 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_20 {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
// TODO: remember to change it in one of future releases!!
|
||||
use_legacy_framed_packet_version: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::old_config_v1_1_29::{
|
||||
ConfigV1_1_29, DebugV1_1_29, GatewayPathsV1_1_29, GatewayV1_1_29, KeysPathsV1_1_29,
|
||||
LoggingSettingsV1_1_29,
|
||||
};
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeysPathsV1_1_28 {
|
||||
pub private_identity_key_file: PathBuf,
|
||||
pub public_identity_key_file: PathBuf,
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_28 {
|
||||
pub keys: KeysPathsV1_1_28,
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_28 {}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_28 {
|
||||
pub gateway: GatewayV1_1_28,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_28,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_28,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_28,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_28 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_28> for ConfigV1_1_29 {
|
||||
fn from(value: ConfigV1_1_28) -> Self {
|
||||
ConfigV1_1_29 {
|
||||
// \/ ADDED
|
||||
save_path: None,
|
||||
// /\ ADDED
|
||||
gateway: GatewayV1_1_29 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
storage_paths: GatewayPathsV1_1_29 {
|
||||
keys: KeysPathsV1_1_29 {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
|
||||
// \/ ADDED
|
||||
network_requester_config: None,
|
||||
// /\ ADDED
|
||||
},
|
||||
|
||||
// \/ ADDED
|
||||
network_requester: Default::default(),
|
||||
// /\ ADDED
|
||||
logging: LoggingSettingsV1_1_29 {},
|
||||
debug: DebugV1_1_29 {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_28 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_28 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_28 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_28 {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Host;
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use super::old_config_v1_1_31::{
|
||||
ConfigV1_1_31, DebugV1_1_31, GatewayPathsV1_1_31, GatewayV1_1_31, KeysPathsV1_1_31,
|
||||
LoggingSettingsV1_1_31, NetworkRequesterV1_1_31,
|
||||
};
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeysPathsV1_1_29 {
|
||||
pub private_identity_key_file: PathBuf,
|
||||
pub public_identity_key_file: PathBuf,
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = PathBuf::deserialize(deserializer)?;
|
||||
if path.as_os_str().is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_29 {
|
||||
pub keys: KeysPathsV1_1_29,
|
||||
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
#[serde(deserialize_with = "de_maybe_path")]
|
||||
pub network_requester_config: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_29 {}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_29 {
|
||||
#[serde(skip)]
|
||||
pub save_path: Option<PathBuf>,
|
||||
|
||||
pub gateway: GatewayV1_1_29,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_29,
|
||||
|
||||
pub network_requester: NetworkRequesterV1_1_29,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_29,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_29,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_29 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_29> for ConfigV1_1_31 {
|
||||
fn from(value: ConfigV1_1_29) -> Self {
|
||||
ConfigV1_1_31 {
|
||||
save_path: value.save_path,
|
||||
|
||||
// \/ ADDED
|
||||
host: Host {
|
||||
// this is a very bad default!
|
||||
public_ips: vec![value.gateway.listening_address],
|
||||
hostname: None,
|
||||
},
|
||||
// /\ ADDED
|
||||
|
||||
// \/ ADDED
|
||||
http: Default::default(),
|
||||
// /\ ADDED
|
||||
gateway: GatewayV1_1_31 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
|
||||
// \/ ADDED
|
||||
clients_wss_port: None,
|
||||
// /\ ADDED
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
// \/ ADDED
|
||||
wireguard: Default::default(),
|
||||
// /\ ADDED
|
||||
storage_paths: GatewayPathsV1_1_31 {
|
||||
keys: KeysPathsV1_1_31 {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
network_requester_config: value.storage_paths.network_requester_config,
|
||||
},
|
||||
network_requester: NetworkRequesterV1_1_31 {
|
||||
enabled: value.network_requester.enabled,
|
||||
},
|
||||
logging: LoggingSettingsV1_1_31 {},
|
||||
debug: DebugV1_1_31 {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_29 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkRequesterV1_1_29 {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequesterV1_1_29 {
|
||||
fn default() -> Self {
|
||||
NetworkRequesterV1_1_29 { enabled: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_29 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_29 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_29 {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use nym_network_defaults::WG_PORT;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use super::old_config_v1_1_36::{
|
||||
ConfigV1_1_36, DebugV1_1_36, GatewayPathsV1_1_36, GatewayV1_1_36, KeysPathsV1_1_36,
|
||||
LoggingSettingsV1_1_36, NetworkRequesterV1_1_36, WireguardPathsV1_1_36, WireguardV1_1_36,
|
||||
};
|
||||
use super::{Host, Http};
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
fn de_maybe_port<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let port = u16::deserialize(deserializer)?;
|
||||
if port == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(port))
|
||||
}
|
||||
}
|
||||
|
||||
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = PathBuf::deserialize(deserializer)?;
|
||||
if path.as_os_str().is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_31 {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
pub host: Host,
|
||||
|
||||
#[serde(default)]
|
||||
pub http: Http,
|
||||
|
||||
pub gateway: GatewayV1_1_31,
|
||||
|
||||
#[serde(default)]
|
||||
// currently not really used for anything useful
|
||||
pub wireguard: WireguardV1_1_31,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_31,
|
||||
|
||||
pub network_requester: NetworkRequesterV1_1_31,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_31,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_31,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_31 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_31> for ConfigV1_1_36 {
|
||||
fn from(value: ConfigV1_1_31) -> Self {
|
||||
Self {
|
||||
save_path: value.save_path,
|
||||
host: value.host,
|
||||
http: value.http,
|
||||
gateway: GatewayV1_1_36 {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
clients_wss_port: value.gateway.clients_wss_port,
|
||||
enabled_statistics: value.gateway.enabled_statistics,
|
||||
statistics_service_url: value.gateway.statistics_service_url,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
wireguard: WireguardV1_1_36 {
|
||||
enabled: value.wireguard.enabled,
|
||||
bind_address: value.wireguard.bind_address,
|
||||
announced_port: value.wireguard.announced_port,
|
||||
private_network_prefix: Default::default(),
|
||||
storage_paths: WireguardPathsV1_1_36 {
|
||||
// no fields (yet)
|
||||
},
|
||||
},
|
||||
storage_paths: GatewayPathsV1_1_36 {
|
||||
keys: KeysPathsV1_1_36 {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
network_requester_config: value.storage_paths.network_requester_config,
|
||||
// \/ ADDED
|
||||
ip_packet_router_config: Default::default(),
|
||||
// /\ ADDED
|
||||
},
|
||||
network_requester: NetworkRequesterV1_1_36 {
|
||||
enabled: value.network_requester.enabled,
|
||||
},
|
||||
// \/ ADDED
|
||||
ip_packet_router: Default::default(),
|
||||
// /\ ADDED
|
||||
logging: LoggingSettingsV1_1_36 {
|
||||
// no fields (yet)
|
||||
},
|
||||
debug: DebugV1_1_36 {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_31 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// If applicable, announced port for listening for secure websocket client traffic.
|
||||
/// (default: None)
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub clients_wss_port: Option<u16>,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardV1_1_31 {
|
||||
/// Specifies whether the wireguard service is enabled on this node.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Socket address this node will use for binding its wireguard interface.
|
||||
/// default: `0.0.0.0:51820`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_port: u16,
|
||||
|
||||
/// Paths for wireguard keys, client registries, etc.
|
||||
pub storage_paths: WireguardPathsV1_1_31,
|
||||
}
|
||||
|
||||
impl Default for WireguardV1_1_31 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
|
||||
announced_port: WG_PORT,
|
||||
storage_paths: WireguardPathsV1_1_31 {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardPathsV1_1_31 {
|
||||
// pub keys:
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_31 {
|
||||
pub keys: KeysPathsV1_1_31,
|
||||
|
||||
/// Path to sqlite database containing all persistent data: messages for offline clients,
|
||||
/// derived shared keys and available client bandwidths.
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
/// Path to the configuration of the embedded network requester.
|
||||
#[serde(deserialize_with = "de_maybe_path")]
|
||||
pub network_requester_config: Option<PathBuf>,
|
||||
// pub node_description: PathBuf,
|
||||
|
||||
// pub cosmos_bip39_mnemonic: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct KeysPathsV1_1_31 {
|
||||
/// Path to file containing private identity key.
|
||||
pub private_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public identity key.
|
||||
pub public_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing private sphinx key.
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public sphinx key.
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkRequesterV1_1_31 {
|
||||
/// Specifies whether network requester service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequesterV1_1_31 {
|
||||
fn default() -> Self {
|
||||
Self { enabled: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_31 {
|
||||
// well, we need to implement something here at some point...
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_31 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_31 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::persistence::paths::GatewayPaths;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use nym_network_defaults::WG_PORT;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use super::persistence::paths::KeysPaths;
|
||||
use super::{Config, Debug, Gateway, Host, Http, NetworkRequester};
|
||||
|
||||
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
fn de_maybe_port<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let port = u16::deserialize(deserializer)?;
|
||||
if port == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(port))
|
||||
}
|
||||
}
|
||||
|
||||
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = PathBuf::deserialize(deserializer)?;
|
||||
if path.as_os_str().is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_GATEWAYS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to gateways's config file.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_36 {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
pub host: Host,
|
||||
|
||||
#[serde(default)]
|
||||
pub http: Http,
|
||||
|
||||
pub gateway: GatewayV1_1_36,
|
||||
|
||||
#[serde(default)]
|
||||
// currently not really used for anything useful
|
||||
pub wireguard: WireguardV1_1_36,
|
||||
|
||||
pub storage_paths: GatewayPathsV1_1_36,
|
||||
|
||||
pub network_requester: NetworkRequesterV1_1_36,
|
||||
|
||||
#[serde(default)]
|
||||
pub ip_packet_router: IpPacketRouterV1_1_36,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV1_1_36,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_36,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_36 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_36> for Config {
|
||||
fn from(value: ConfigV1_1_36) -> Self {
|
||||
Self {
|
||||
save_path: value.save_path,
|
||||
host: value.host,
|
||||
http: value.http,
|
||||
gateway: Gateway {
|
||||
version: value.gateway.version,
|
||||
id: value.gateway.id,
|
||||
only_coconut_credentials: value.gateway.only_coconut_credentials,
|
||||
listening_address: value.gateway.listening_address,
|
||||
mix_port: value.gateway.mix_port,
|
||||
clients_port: value.gateway.clients_port,
|
||||
clients_wss_port: value.gateway.clients_wss_port,
|
||||
nym_api_urls: value.gateway.nym_api_urls,
|
||||
nyxd_urls: value.gateway.nyxd_urls,
|
||||
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
|
||||
},
|
||||
storage_paths: GatewayPaths {
|
||||
keys: KeysPaths {
|
||||
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
|
||||
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
|
||||
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
|
||||
},
|
||||
clients_storage: value.storage_paths.clients_storage,
|
||||
network_requester_config: value.storage_paths.network_requester_config,
|
||||
// \/ ADDED
|
||||
ip_packet_router_config: Default::default(),
|
||||
// /\ ADDED
|
||||
},
|
||||
network_requester: NetworkRequester {
|
||||
enabled: value.network_requester.enabled,
|
||||
},
|
||||
// \/ ADDED
|
||||
ip_packet_router: Default::default(),
|
||||
// /\ ADDED
|
||||
logging: LoggingSettings {
|
||||
// no fields (yet)
|
||||
},
|
||||
debug: Debug {
|
||||
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
|
||||
presence_sending_delay: value.debug.presence_sending_delay,
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct GatewayV1_1_36 {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
#[serde(default)]
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// If applicable, announced port for listening for secure websocket client traffic.
|
||||
/// (default: None)
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub clients_wss_port: Option<u16>,
|
||||
|
||||
/// Whether gateway collects and sends anonymized statistics
|
||||
pub enabled_statistics: bool,
|
||||
|
||||
/// Domain address of the statistics service
|
||||
pub statistics_service_url: Url,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[serde(alias = "validator_nymd_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardV1_1_36 {
|
||||
/// Specifies whether the wireguard service is enabled on this node.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Socket address this node will use for binding its wireguard interface.
|
||||
/// default: `0.0.0.0:51820`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_port: u16,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard.
|
||||
/// The maximum value for IPv4 is 32 and for IPv6 is 128
|
||||
pub private_network_prefix: u8,
|
||||
|
||||
/// Paths for wireguard keys, client registries, etc.
|
||||
pub storage_paths: WireguardPathsV1_1_36,
|
||||
}
|
||||
|
||||
impl Default for WireguardV1_1_36 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
|
||||
announced_port: WG_PORT,
|
||||
storage_paths: WireguardPathsV1_1_36 {},
|
||||
private_network_prefix: 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardPathsV1_1_36 {
|
||||
// pub keys:
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayPathsV1_1_36 {
|
||||
pub keys: KeysPathsV1_1_36,
|
||||
|
||||
/// Path to sqlite database containing all persistent data: messages for offline clients,
|
||||
/// derived shared keys and available client bandwidths.
|
||||
#[serde(alias = "persistent_storage")]
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
/// Path to the configuration of the embedded network requester.
|
||||
#[serde(deserialize_with = "de_maybe_path")]
|
||||
pub network_requester_config: Option<PathBuf>,
|
||||
// pub node_description: PathBuf,
|
||||
|
||||
// pub cosmos_bip39_mnemonic: PathBuf,
|
||||
/// Path to the configuration of the embedded ip packet router.
|
||||
#[serde(deserialize_with = "de_maybe_path")]
|
||||
pub ip_packet_router_config: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct KeysPathsV1_1_36 {
|
||||
/// Path to file containing private identity key.
|
||||
pub private_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public identity key.
|
||||
pub public_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing private sphinx key.
|
||||
pub private_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public sphinx key.
|
||||
pub public_sphinx_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct NetworkRequesterV1_1_36 {
|
||||
/// Specifies whether network requester service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequesterV1_1_36 {
|
||||
fn default() -> Self {
|
||||
Self { enabled: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettingsV1_1_36 {
|
||||
// well, we need to implement something here at some point...
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_36 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub presence_sending_delay: Duration,
|
||||
|
||||
/// Length of filenames for new client messages.
|
||||
pub stored_messages_filename_length: u16,
|
||||
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_36 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct IpPacketRouterV1_1_36 {
|
||||
/// Specifies whether ip packet router service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for IpPacketRouterV1_1_36 {
|
||||
fn default() -> Self {
|
||||
Self { enabled: false }
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,6 @@ struct Cli {
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
/// Force run the binary bypassing the deprecation in favour of nym-node
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) force_run: bool,
|
||||
|
||||
/// Flag used for disabling the printed banner in tty.
|
||||
#[clap(long)]
|
||||
pub(crate) no_banner: bool,
|
||||
|
||||
@@ -257,7 +257,6 @@ impl<St> Gateway<St> {
|
||||
.ok_or(GatewayError::UnspecifiedAuthenticatorConfig)?;
|
||||
let (router_tx, mut router_rx) = oneshot::channel();
|
||||
let (auth_mix_sender, auth_mix_receiver) = mpsc::unbounded();
|
||||
let (peer_response_tx, peer_response_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let router_shutdown = shutdown.fork("message_router");
|
||||
let transceiver = LocalGateway::new(
|
||||
*self.identity_keypair.public_key(),
|
||||
@@ -270,7 +269,6 @@ impl<St> Gateway<St> {
|
||||
let mut authenticator_server = nym_authenticator::Authenticator::new(
|
||||
opts.config.clone(),
|
||||
wireguard_data.inner.clone(),
|
||||
peer_response_rx,
|
||||
)
|
||||
.with_custom_gateway_transceiver(Box::new(transceiver))
|
||||
.with_shutdown(shutdown.fork("authenticator"))
|
||||
@@ -301,8 +299,7 @@ impl<St> Gateway<St> {
|
||||
MessageRouter::new(auth_mix_receiver, packet_router)
|
||||
.start_with_shutdown(router_shutdown);
|
||||
|
||||
let wg_api =
|
||||
nym_wireguard::start_wireguard(shutdown, wireguard_data, peer_response_tx).await?;
|
||||
let wg_api = nym_wireguard::start_wireguard(shutdown, wireguard_data).await?;
|
||||
|
||||
Ok(StartedAuthenticator {
|
||||
wg_api,
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"version": "independent"
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::Cli;
|
||||
use anyhow::bail;
|
||||
use clap::CommandFactory;
|
||||
use clap::Subcommand;
|
||||
use colored::Colorize;
|
||||
@@ -24,6 +23,7 @@ mod init;
|
||||
mod node_details;
|
||||
mod run;
|
||||
mod sign;
|
||||
mod upgrade_helpers;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
@@ -67,12 +67,6 @@ struct OverrideConfig {
|
||||
pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> {
|
||||
let bin_name = "nym-mixnode";
|
||||
|
||||
if !args.force_run {
|
||||
let msg = "standalone mixnodes have been deprecated - please migrate to a `nym-node` via `nym-node migrate mixnode` command";
|
||||
error!("{msg}");
|
||||
bail!("{msg}")
|
||||
}
|
||||
|
||||
warn!("standalone mixnodes have been deprecated - please consider migrating it to a `nym-node` via `nym-node migrate mixnode` command");
|
||||
if std::io::stdout().is_terminal() {
|
||||
// if user is running it in terminal session,
|
||||
@@ -131,6 +125,8 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) {
|
||||
}
|
||||
|
||||
fn try_load_current_config(id: &str) -> Result<Config, MixnodeError> {
|
||||
upgrade_helpers::try_upgrade_config(id)?;
|
||||
|
||||
Config::read_from_default_path(id).map_err(|err| {
|
||||
error!(
|
||||
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use log::info;
|
||||
use nym_mixnode::config::old_config_v1_1_21::ConfigV1_1_21;
|
||||
use nym_mixnode::config::old_config_v1_1_32::ConfigV1_1_32;
|
||||
use nym_mixnode::config::{default_config_filepath, Config};
|
||||
use nym_mixnode::error::MixnodeError;
|
||||
|
||||
fn try_upgrade_v1_1_21_config(id: &str) -> Result<bool, MixnodeError> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+)
|
||||
let Ok(old_config) = ConfigV1_1_21::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the mixnode is using <= v1.1.21 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_32 = old_config.into();
|
||||
|
||||
let updated: Config = updated_step1.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| MixnodeError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_32_config(id: &str) -> Result<bool, MixnodeError> {
|
||||
// explicitly load it as v1.1.32 (which is incompatible with the current, i.e. 1.1.22+)
|
||||
let Ok(old_config) = ConfigV1_1_32::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the mixnode is using <= v1.1.32 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated
|
||||
.save_to_default_location()
|
||||
.map_err(|err| MixnodeError::ConfigSaveFailure {
|
||||
path: default_config_filepath(id),
|
||||
id: id.to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn try_upgrade_config(id: &str) -> Result<(), MixnodeError> {
|
||||
if try_upgrade_v1_1_21_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_32_config(id)? {
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -22,6 +22,8 @@ use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
pub mod old_config_v1_1_21;
|
||||
pub mod old_config_v1_1_32;
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::old_config_v1_1_32::{
|
||||
ConfigV1_1_32, DebugV1_1_32, MixNodeV1_1_32, VerlocV1_1_32,
|
||||
};
|
||||
use crate::config::persistence::paths::{KeysPaths, MixNodePaths};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_validator_client::nyxd;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_MIX_LISTENING_PORT: u16 = 1789;
|
||||
const DEFAULT_VERLOC_LISTENING_PORT: u16 = 1790;
|
||||
const DEFAULT_HTTP_API_LISTENING_PORT: u16 = 8000;
|
||||
const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
const DESCRIPTION_FILE: &str = "description.toml";
|
||||
|
||||
// 'RTT MEASUREMENT'
|
||||
const DEFAULT_PACKETS_PER_NODE: usize = 100;
|
||||
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
|
||||
const DEFAULT_BATCH_SIZE: usize = 50;
|
||||
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
|
||||
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
|
||||
|
||||
// 'DEBUG'
|
||||
const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000);
|
||||
const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
pub(super) fn de_ipaddr_from_maybe_str_socks_addr<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<IpAddr, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if let Ok(socket_addr) = SocketAddr::from_str(&s) {
|
||||
Ok(socket_addr.ip())
|
||||
} else {
|
||||
IpAddr::from_str(&s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_all_address() -> IpAddr {
|
||||
"0.0.0.0".parse().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_21 {
|
||||
mixnode: MixNodeV1_1_21,
|
||||
|
||||
#[serde(default)]
|
||||
verloc: VerlocV1_1_21,
|
||||
#[serde(default)]
|
||||
logging: LoggingV1_1_21,
|
||||
#[serde(default)]
|
||||
debug: DebugV1_1_21,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_21> for ConfigV1_1_32 {
|
||||
fn from(value: ConfigV1_1_21) -> Self {
|
||||
let node_description =
|
||||
ConfigV1_1_21::default_config_directory(&value.mixnode.id).join(DESCRIPTION_FILE);
|
||||
|
||||
ConfigV1_1_32 {
|
||||
mixnode: MixNodeV1_1_32 {
|
||||
version: value.mixnode.version,
|
||||
id: value.mixnode.id,
|
||||
listening_address: value.mixnode.listening_address,
|
||||
mix_port: value.mixnode.mix_port,
|
||||
verloc_port: value.mixnode.verloc_port,
|
||||
http_api_port: value.mixnode.http_api_port,
|
||||
nym_api_urls: value.mixnode.nym_api_urls,
|
||||
},
|
||||
storage_paths: MixNodePaths {
|
||||
keys: KeysPaths {
|
||||
private_identity_key_file: value.mixnode.private_identity_key_file,
|
||||
public_identity_key_file: value.mixnode.public_identity_key_file,
|
||||
private_sphinx_key_file: value.mixnode.private_sphinx_key_file,
|
||||
public_sphinx_key_file: value.mixnode.public_sphinx_key_file,
|
||||
},
|
||||
node_description,
|
||||
},
|
||||
verloc: value.verloc.into(),
|
||||
logging: value.logging.into(),
|
||||
debug: value.debug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_21 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("mixnodes")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
struct MixNodeV1_1_21 {
|
||||
version: String,
|
||||
id: String,
|
||||
#[serde(deserialize_with = "de_ipaddr_from_maybe_str_socks_addr")]
|
||||
listening_address: IpAddr,
|
||||
announce_address: String,
|
||||
mix_port: u16,
|
||||
verloc_port: u16,
|
||||
http_api_port: u16,
|
||||
private_identity_key_file: PathBuf,
|
||||
public_identity_key_file: PathBuf,
|
||||
private_sphinx_key_file: PathBuf,
|
||||
public_sphinx_key_file: PathBuf,
|
||||
nym_api_urls: Vec<Url>,
|
||||
nym_root_directory: PathBuf,
|
||||
wallet_address: Option<nyxd::AccountId>,
|
||||
}
|
||||
|
||||
impl Default for MixNodeV1_1_21 {
|
||||
fn default() -> Self {
|
||||
MixNodeV1_1_21 {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
listening_address: bind_all_address(),
|
||||
announce_address: "127.0.0.1".to_string(),
|
||||
mix_port: DEFAULT_MIX_LISTENING_PORT,
|
||||
verloc_port: DEFAULT_VERLOC_LISTENING_PORT,
|
||||
http_api_port: DEFAULT_HTTP_API_LISTENING_PORT,
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
private_sphinx_key_file: Default::default(),
|
||||
public_sphinx_key_file: Default::default(),
|
||||
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
|
||||
nym_root_directory: ConfigV1_1_21::default_root_directory(),
|
||||
wallet_address: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct LoggingV1_1_21 {}
|
||||
|
||||
impl From<LoggingV1_1_21> for LoggingSettings {
|
||||
fn from(_value: LoggingV1_1_21) -> Self {
|
||||
LoggingSettings {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct VerlocV1_1_21 {
|
||||
packets_per_node: usize,
|
||||
connection_timeout: Duration,
|
||||
packet_timeout: Duration,
|
||||
delay_between_packets: Duration,
|
||||
tested_nodes_batch_size: usize,
|
||||
testing_interval: Duration,
|
||||
retry_timeout: Duration,
|
||||
}
|
||||
|
||||
impl From<VerlocV1_1_21> for VerlocV1_1_32 {
|
||||
fn from(value: VerlocV1_1_21) -> Self {
|
||||
VerlocV1_1_32 {
|
||||
packets_per_node: value.packets_per_node,
|
||||
connection_timeout: value.connection_timeout,
|
||||
packet_timeout: value.packet_timeout,
|
||||
delay_between_packets: value.delay_between_packets,
|
||||
tested_nodes_batch_size: value.tested_nodes_batch_size,
|
||||
testing_interval: value.testing_interval,
|
||||
retry_timeout: value.retry_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VerlocV1_1_21 {
|
||||
fn default() -> Self {
|
||||
VerlocV1_1_21 {
|
||||
packets_per_node: DEFAULT_PACKETS_PER_NODE,
|
||||
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
|
||||
packet_timeout: DEFAULT_PACKET_TIMEOUT,
|
||||
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
|
||||
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
|
||||
testing_interval: DEFAULT_TESTING_INTERVAL,
|
||||
retry_timeout: DEFAULT_RETRY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
struct DebugV1_1_21 {
|
||||
#[serde(with = "humantime_serde")]
|
||||
node_stats_logging_delay: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
node_stats_updating_delay: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
initial_connection_timeout: Duration,
|
||||
|
||||
maximum_connection_buffer_size: usize,
|
||||
|
||||
use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl From<DebugV1_1_21> for DebugV1_1_32 {
|
||||
fn from(value: DebugV1_1_21) -> Self {
|
||||
DebugV1_1_32 {
|
||||
node_stats_logging_delay: value.node_stats_logging_delay,
|
||||
node_stats_updating_delay: value.node_stats_updating_delay,
|
||||
packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.maximum_connection_buffer_size,
|
||||
use_legacy_framed_packet_version: value.use_legacy_framed_packet_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_21 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_21 {
|
||||
node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY,
|
||||
node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY,
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
use_legacy_framed_packet_version: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config;
|
||||
use crate::config::persistence::paths::MixNodePaths;
|
||||
use crate::config::{Config, Debug, MixNode, Verloc};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_MIXNODES_DIR: &str = "mixnodes";
|
||||
|
||||
// 'RTT MEASUREMENT'
|
||||
const DEFAULT_PACKETS_PER_NODE: usize = 100;
|
||||
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
|
||||
const DEFAULT_BATCH_SIZE: usize = 50;
|
||||
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
|
||||
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
|
||||
|
||||
// 'DEBUG'
|
||||
const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000);
|
||||
const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000);
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
|
||||
/// Derive default path to mixnodes's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/mixnodes/<id>/config`
|
||||
fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_MIXNODES_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to mixnodes's config file.
|
||||
/// It should get resolved to `$HOME/.nym/mixnodes/<id>/config/config.toml`
|
||||
fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_32 {
|
||||
pub mixnode: MixNodeV1_1_32,
|
||||
|
||||
// i hope this laziness is not going to backfire...
|
||||
pub storage_paths: MixNodePaths,
|
||||
|
||||
#[serde(default)]
|
||||
pub verloc: VerlocV1_1_32,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettings,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV1_1_32,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_32 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_32> for Config {
|
||||
fn from(value: ConfigV1_1_32) -> Self {
|
||||
Config {
|
||||
// \/ ADDED
|
||||
save_path: None,
|
||||
// /\ ADDED
|
||||
|
||||
// \/ ADDED
|
||||
host: config::Host {
|
||||
// this is a very bad default!
|
||||
public_ips: vec![value.mixnode.listening_address],
|
||||
hostname: None,
|
||||
},
|
||||
// /\ ADDED
|
||||
|
||||
// \/ ADDED
|
||||
http: config::Http {
|
||||
bind_address: SocketAddr::new(
|
||||
value.mixnode.listening_address,
|
||||
value.mixnode.http_api_port,
|
||||
),
|
||||
landing_page_assets_path: None,
|
||||
metrics_key: None,
|
||||
},
|
||||
// /\ ADDED
|
||||
mixnode: MixNode {
|
||||
version: value.mixnode.version,
|
||||
id: value.mixnode.id,
|
||||
listening_address: value.mixnode.listening_address,
|
||||
mix_port: value.mixnode.mix_port,
|
||||
verloc_port: value.mixnode.verloc_port,
|
||||
nym_api_urls: value.mixnode.nym_api_urls,
|
||||
},
|
||||
storage_paths: value.storage_paths,
|
||||
verloc: value.verloc.into(),
|
||||
logging: value.logging,
|
||||
debug: value.debug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct MixNodeV1_1_32 {
|
||||
/// Version of the mixnode for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular mixnode.
|
||||
pub id: String,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for verloc traffic.
|
||||
/// (default: 1790)
|
||||
pub verloc_port: u16,
|
||||
|
||||
/// Port used for listening for http requests.
|
||||
/// (default: 8000)
|
||||
pub http_api_port: u16,
|
||||
|
||||
/// Addresses to nym APIs from which the node gets the view of the network.
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct VerlocV1_1_32 {
|
||||
/// Specifies number of echo packets sent to each node during a measurement run.
|
||||
pub packets_per_node: usize,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the connection to get established.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub connection_timeout: Duration,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_timeout: Duration,
|
||||
|
||||
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub delay_between_packets: Duration,
|
||||
|
||||
/// Specifies number of nodes being tested at once.
|
||||
pub tested_nodes_batch_size: usize,
|
||||
|
||||
/// Specifies delay between subsequent test runs.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub testing_interval: Duration,
|
||||
|
||||
/// Specifies delay between attempting to run the measurement again if the previous run failed
|
||||
/// due to being unable to get the list of nodes.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub retry_timeout: Duration,
|
||||
}
|
||||
|
||||
impl From<VerlocV1_1_32> for Verloc {
|
||||
fn from(value: VerlocV1_1_32) -> Self {
|
||||
Verloc {
|
||||
packets_per_node: value.packets_per_node,
|
||||
connection_timeout: value.connection_timeout,
|
||||
packet_timeout: value.packet_timeout,
|
||||
delay_between_packets: value.delay_between_packets,
|
||||
tested_nodes_batch_size: value.tested_nodes_batch_size,
|
||||
testing_interval: value.testing_interval,
|
||||
retry_timeout: value.retry_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VerlocV1_1_32 {
|
||||
fn default() -> Self {
|
||||
VerlocV1_1_32 {
|
||||
packets_per_node: DEFAULT_PACKETS_PER_NODE,
|
||||
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
|
||||
packet_timeout: DEFAULT_PACKET_TIMEOUT,
|
||||
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
|
||||
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
|
||||
testing_interval: DEFAULT_TESTING_INTERVAL,
|
||||
retry_timeout: DEFAULT_RETRY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DebugV1_1_32 {
|
||||
/// Delay between each subsequent node statistics being logged to the console
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_stats_logging_delay: Duration,
|
||||
|
||||
/// Delay between each subsequent node statistics being updated
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_stats_updating_delay: Duration,
|
||||
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl From<DebugV1_1_32> for Debug {
|
||||
fn from(value: DebugV1_1_32) -> Self {
|
||||
Debug {
|
||||
node_stats_logging_delay: value.node_stats_logging_delay,
|
||||
node_stats_updating_delay: value.node_stats_updating_delay,
|
||||
packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: value.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: value.maximum_connection_buffer_size,
|
||||
use_legacy_framed_packet_version: value.use_legacy_framed_packet_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DebugV1_1_32 {
|
||||
fn default() -> Self {
|
||||
DebugV1_1_32 {
|
||||
node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY,
|
||||
node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY,
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,6 @@ struct Cli {
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
/// Force run the binary bypassing the deprecation in favour of nym-node
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) force_run: bool,
|
||||
|
||||
/// Flag used for disabling the printed banner in tty.
|
||||
#[clap(long)]
|
||||
pub(crate) no_banner: bool,
|
||||
|
||||
@@ -612,15 +612,9 @@ pub struct NymNodeDescription {
|
||||
// for now we only care about their ws/wss situation, nothing more
|
||||
pub mixnet_websockets: WebSockets,
|
||||
|
||||
#[serde(default = "default_node_role")]
|
||||
pub role: NodeRole,
|
||||
}
|
||||
|
||||
// For backwards compatibility, set a slightly artificial default
|
||||
fn default_node_role() -> NodeRole {
|
||||
NodeRole::Inactive
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct DescribedGateway {
|
||||
pub bond: GatewayBond,
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@nymproject/eslint-config-react-typescript": "workspace:^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
|
||||
@@ -32,7 +32,7 @@ use nym_node_http_api::{NymNodeHTTPServer, NymNodeRouter};
|
||||
use nym_sphinx_acknowledgements::AckKey;
|
||||
use nym_sphinx_addressing::Recipient;
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
|
||||
use nym_wireguard::{peer_controller::PeerControlMessage, WireguardGatewayData};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::path::Path;
|
||||
@@ -264,7 +264,7 @@ impl ExitGatewayData {
|
||||
|
||||
pub struct WireguardData {
|
||||
inner: WireguardGatewayData,
|
||||
peer_rx: UnboundedReceiver<PeerControlRequest>,
|
||||
peer_rx: UnboundedReceiver<PeerControlMessage>,
|
||||
}
|
||||
|
||||
impl WireguardData {
|
||||
|
||||
+16
-15
@@ -5,20 +5,20 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "run-s webpack:prod tauri:build",
|
||||
"dev": "run-p tauri:dev webpack:dev",
|
||||
"dev": "pnpm run-p tauri:dev webpack:dev",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"prebuild": "yarn --cwd .. build",
|
||||
"prestorybook": "yarn --cwd .. build",
|
||||
"prewebpack:dev": "yarn --cwd .. build",
|
||||
"prebuild": "pnpm -C .. build",
|
||||
"prestorybook": "pnpm -C .. build",
|
||||
"prewebpack:dev": "pnpm -C .. run build",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"storybook:build": "build-storybook",
|
||||
"tauri:build": "yarn tauri build",
|
||||
"tauri:dev": "yarn tauri dev",
|
||||
"tauri:build": "pnpm tauri build",
|
||||
"tauri:dev": "pnpm tauri dev",
|
||||
"tsc": "tsc --noEmit true",
|
||||
"tsc:watch": "tsc --noEmit true --watch",
|
||||
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
|
||||
"webpack:prod": "yarn webpack --progress --config webpack.prod.js"
|
||||
"webpack:dev": "pnpm webpack serve --config webpack.dev.js",
|
||||
"webpack:prod": "pnpm webpack --progress --config webpack.prod.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.7.0",
|
||||
@@ -28,10 +28,11 @@
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@mui/utils": "^5.7.0",
|
||||
"@nymproject/mui-theme": "^1.0.0",
|
||||
"@nymproject/node-tester": "^1.2.3",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/types": "^1.0.0",
|
||||
"@nymproject/mui-theme": "workspace:^1.0.0",
|
||||
"@nymproject/node-tester": "1.2.4-rc.1",
|
||||
"@nymproject/react": "workspace:^1.0.0",
|
||||
"@nymproject/types": "workspace:^1.0.0",
|
||||
"@nymproject/webpack": "workspace:^1.0.0",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
@@ -42,7 +43,6 @@
|
||||
"joi": "^17.11.0",
|
||||
"lodash": "^4.17.21",
|
||||
"notistack": "^2.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -63,7 +63,7 @@
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@nymproject/eslint-config-react-typescript": "workspace:^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
@@ -73,6 +73,7 @@
|
||||
"@types/big.js": "^6.1.6",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/lodash": "^4.17.6",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react": "^18.0.26",
|
||||
@@ -125,4 +126,4 @@
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"private": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +507,7 @@ struct NetworkDetails {
|
||||
stake_denom: DenomDetailsOwned,
|
||||
mixnet_contract_address: String,
|
||||
vesting_contract_address: String,
|
||||
statistics_service_url: String,
|
||||
validators: Vec<ValidatorDetails>,
|
||||
}
|
||||
|
||||
@@ -526,6 +527,7 @@ impl From<NymNetworkDetails> for NetworkDetails {
|
||||
.contracts
|
||||
.vesting_contract_address
|
||||
.unwrap_or_default(),
|
||||
statistics_service_url: "".to_string(),
|
||||
validators: details.endpoints,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Playground } from '@nymproject/react/playground/Playground';
|
||||
|
||||
export default {
|
||||
title: 'Playground',
|
||||
component: Playground,
|
||||
} as ComponentMeta<typeof Playground>;
|
||||
|
||||
export const AllControls = () => <Playground />;
|
||||
+37
-34
@@ -3,55 +3,58 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "Apache 2.0",
|
||||
"workspaces": [
|
||||
"dist/wasm/**",
|
||||
"dist/node/**",
|
||||
"dist/ts/**",
|
||||
"sdk/typescript/packages/mui-theme",
|
||||
"sdk/typescript/packages/react-components",
|
||||
"sdk/typescript/packages/validator-client",
|
||||
"ts-packages/*",
|
||||
"nym-wallet",
|
||||
"explorer",
|
||||
"explorer-nextjs",
|
||||
"types",
|
||||
"clients/validator"
|
||||
],
|
||||
"scripts": {
|
||||
"nuke": "npx rimraf **/node_modules node_modules",
|
||||
"scrub": "npx rimraf **/dist dist",
|
||||
"clean": "lerna run clean",
|
||||
"build": "run-s build:types build:packages",
|
||||
"build": "pnpm build:types && pnpm build:packages",
|
||||
"build:wasm": "make sdk-wasm-build",
|
||||
"build:types": "lerna run --scope @nymproject/types build --stream",
|
||||
"build:types": "pnpm run --filter @nymproject/types --stream build",
|
||||
"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:storybook": "yarn build && yarn dev:on && run-p build:react-example build:playground && yarn build:ci:storybook:collect-artifacts",
|
||||
"build:packages:theme": "pnpm run --filter @nymproject/mui-theme build",
|
||||
"build:packages:react": "pnpm run --filter @nymproject/react build",
|
||||
"build:react-example": "pnpm run --filter @nymproject/react-webpack-with-theme-example --stream build ",
|
||||
"build:playground": "pnpm run --filter @nymproject/react --stream storybook:build",
|
||||
"build:ci:storybook": "pnpm run build && pnpm run dev:on && run-p build:react-example build:playground && pnpm 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/contract-clients,@nymproject/sdk-react,@nymproject/mix-fetch,@nymproject/nodejs-client,@nymproject/mix-fetch-node}' build --stream",
|
||||
"docs:prod:build": "run-s docs:prod:build:ws",
|
||||
"docs:prod:build:ws": "lerna run docs:prod:build --stream",
|
||||
"prebuild:ci": "pnpm run dev:on",
|
||||
"build:ci": "pnpm dlx npm-run-all build:types build:packages build:wasm build:ci:sdk",
|
||||
"postbuild:ci": "pnpm run dev:off",
|
||||
"build:ci:sdk": "pnpm run --filter '{@nymproject/sdk,@nymproject/node-tester,@nymproject/contract-clients,@nymproject/sdk-react,@nymproject/mix-fetch,@nymproject/nodejs-client,@nymproject/mix-fetch-node}' --stream build",
|
||||
"docs:prod:build": "pnpm docs:prod:build:ws",
|
||||
"docs:prod:build:ws": "pnpm -r docs:prod:build",
|
||||
"sdk:build": "./sdk/typescript/scripts/build-prod-sdk.sh",
|
||||
"sdk:publish": "./sdk/typescript/scripts/publish.sh",
|
||||
"lint": "lerna run lint --stream",
|
||||
"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",
|
||||
"lint": "pnpm -r run --stream lint",
|
||||
"lint:fix": "pnpm -r run --stream lint:fix",
|
||||
"tsc": "pnpm -r run --stream tsc",
|
||||
"types:lint:fix": "pnpm run lint:fix --filter @nymproject/types --filter @nymproject/nym-wallet-app",
|
||||
"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",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"@npmcli/node-gyp": "^3.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"node-gyp": "^9.3.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"tslog": "3.3.3"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"d3-color@<3.1.0": ">=3.1.0",
|
||||
"trim@<0.0.3": ">=0.0.3",
|
||||
"sharp@<0.30.5": ">=0.30.5",
|
||||
"postcss@<8.4.31": ">=8.4.31",
|
||||
"sharp@<0.32.6": ">=0.32.6",
|
||||
"trim-newlines@<3.0.1": ">=3.0.1",
|
||||
"semver@>=7.0.0 <7.5.2": ">=7.5.2",
|
||||
"xml2js@<0.5.0": ">=0.5.0",
|
||||
"webpack-dev-middleware@<=5.3.3": ">=5.3.4",
|
||||
"@babel/traverse@<7.23.2": ">=7.23.2",
|
||||
"ws@>=8.0.0 <8.17.1": ">=8.17.1",
|
||||
"axios@>=0.8.1 <0.28.0": ">=0.28.0",
|
||||
"protobufjs@>=6.10.0 <6.11.4": ">=6.11.4",
|
||||
"braces@<3.0.3": ">=3.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+39228
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
packages:
|
||||
- clients/validator
|
||||
- dist/wasm/**
|
||||
- dist/node/**
|
||||
- dist/ts/**
|
||||
- explorer-nextjs
|
||||
- sdk/typescript/packages/react-components
|
||||
- sdk/typescript/packages/validator-client
|
||||
- sdk/typescript/packages/mui-theme
|
||||
- sdk/typescript/packages/**
|
||||
- sdk/typescript/examples/**
|
||||
- sdk/typescript/codegen/**
|
||||
- ts-packages/**
|
||||
- wallet
|
||||
@@ -15,7 +15,7 @@
|
||||
"docs:generate:prod": "typedoc --basePath ./docs/tsdoc/nymproject/contract-clients/",
|
||||
"docs:prod:build": "scripts/build-prod-docs-collect.sh",
|
||||
"docs:serve": "reload -b -d ./docs -p 3000",
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"yarn docs:generate\""
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"pnpm docs:generate\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cosmwasm/ts-codegen": "^0.35.3",
|
||||
@@ -26,5 +26,9 @@
|
||||
"typescript": "^4.6.2"
|
||||
},
|
||||
"private": false,
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
"types": "./dist/index.d.ts",
|
||||
"dependencies": {
|
||||
"@cosmjs/amino": "^0.32.4",
|
||||
"@cosmjs/cosmwasm-stargate": "^0.29.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ set -o pipefail
|
||||
rm -rf ../../../../dist/ts/docs/tsdoc/nymproject/contract-clients || true
|
||||
|
||||
# run the build
|
||||
yarn docs:generate:prod
|
||||
pnpm docs:generate:prod
|
||||
|
||||
# move the output outside of the yarn/npm workspaces
|
||||
mkdir -p ../../../../dist/ts/docs/tsdoc/nymproject
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"build": "npx parcel build",
|
||||
"build:serve": "npx serve dist",
|
||||
"lint": "eslint src",
|
||||
"lint": "echo not implemented",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"start": "npx parcel",
|
||||
"test": "jest",
|
||||
@@ -41,4 +41,4 @@
|
||||
"private": false,
|
||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||
"source": "src/index.html"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
"build": "webpack build --progress --config webpack.prod.js",
|
||||
"build:dev": "webpack build --progress",
|
||||
"build:serve": "npx serve dist",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"lint": "echo not implemented",
|
||||
"start": "webpack serve --progress --port 3000",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
@@ -62,4 +61,4 @@
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"private": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "An example project that uses WASM, React, Webpack, Typescript and the Nym theme + components library",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build": "webpack build --progress --config webpack.prod.js",
|
||||
"build": "echo not implemented",
|
||||
"build:dev": "webpack build --progress",
|
||||
"build:serve": "npx serve dist",
|
||||
"lint": "eslint src",
|
||||
@@ -78,7 +78,7 @@
|
||||
"typescript": "^4.6.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
@@ -90,4 +90,4 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { createNymMixnetClient, IWebWorkerEvents, MimeTypes, NymClientConfig, NymMixnetClient } from '@nymproject/sdk';
|
||||
|
||||
export interface BinaryMessageHeaders {
|
||||
filename: string;
|
||||
mimeType: string;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('app'));
|
||||
const element = document.getElementById('app');
|
||||
|
||||
if (!element) {
|
||||
throw new Error('Element not found');
|
||||
}
|
||||
|
||||
createRoot(element).render(<App />);
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@mui/material": "^5.0.1",
|
||||
"@mui/styles": "^5.0.1",
|
||||
"@mui/icons-material": "^5.5.0",
|
||||
"@mui/lab": "^5.0.0-alpha.72",
|
||||
"@nymproject/mui-theme": "^1.0.0",
|
||||
"@nymproject/react": "^1.0.0"
|
||||
"@mui/material": "^5.0.1",
|
||||
"@mui/styles": "^5.0.1",
|
||||
"@nymproject/mui-theme": "workspace:^1.0.0",
|
||||
"@nymproject/react": "workspace:^1.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
@@ -19,15 +19,16 @@
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@nymproject/webpack": "^1.0.0",
|
||||
"@nymproject/eslint-config-react-typescript": "workspace:^1.0.0",
|
||||
"@nymproject/webpack": "workspace:^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"babel-loader": "^8.3.0",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Container, Grid, Typography } from '@mui/material';
|
||||
import { NymLogo } from '@nymproject/react/logo/NymLogo';
|
||||
import { Playground } from '@nymproject/react/playground/Playground';
|
||||
import { useIsMounted } from '@nymproject/react/hooks/useIsMounted';
|
||||
import { NymLogo } from '@nymproject/react';
|
||||
import { useIsMounted } from '@nymproject/react';
|
||||
import { NymThemeProvider } from '@nymproject/mui-theme';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
@@ -65,8 +64,6 @@ export const Content: FCWithChildren = () => {
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
<h1>Component playground</h1>
|
||||
<Playground />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import React, { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { App } from '../App';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(() => {
|
||||
render(<App />);
|
||||
});
|
||||
it('should render without exploding', () => {
|
||||
const { container } = render(<App />);
|
||||
expect(container.firstChild).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@
|
||||
"test:watch": "jest --watch",
|
||||
"tsc": "tsc",
|
||||
"tsc:watch": "tsc --watch",
|
||||
"lint": "eslint src",
|
||||
"lint": "echo not implemented",
|
||||
"lint:fix": "eslint src --fix"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
"docs:generate:prod": "typedoc --basePath ./docs/tsdoc/nymproject/sdk/",
|
||||
"docs:prod:build": "scripts/build-prod-docs-collect.sh",
|
||||
"docs:serve": "reload -b -d ./docs -p 3000",
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"yarn docs:generate\"",
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"pnpm docs:generate\"",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"start": "tsc -w",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'yarn build:dev:esm'",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'pnpm build:dev:esm'",
|
||||
"test": "node --experimental-fetch --experimental-vm-modules node_modules/jest/bin/jest.js -c=jest.config.mjs --no-cache",
|
||||
"tsc": "tsc --noEmit true"
|
||||
},
|
||||
@@ -42,6 +42,7 @@
|
||||
"@rollup/plugin-wasm": "^6.1.1",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"eslint": "^8.10.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,18 +24,19 @@
|
||||
"docs:generate:prod": "typedoc --basePath ./docs/tsdoc/nymproject/sdk/",
|
||||
"docs:prod:build": "scripts/build-prod-docs-collect.sh",
|
||||
"docs:serve": "reload -b -d ./docs -p 3000",
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"yarn docs:generate\"",
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"pnpm docs:generate\"",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"prebuild": "node scripts/showDependencyLocation.cjs",
|
||||
"start": "tsc -w",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'yarn build:dev:esm'",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'pnpm build:dev:esm'",
|
||||
"test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js -c=jest.config.mjs --no-cache",
|
||||
"tsc": "tsc --noEmit true"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/mix-fetch-wasm": ">=1.2.4-rc.2 || ^1",
|
||||
"comlink": "^4.3.1"
|
||||
"comlink": "^4.3.1",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
@@ -43,7 +44,7 @@
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@nymproject/eslint-config-react-typescript": "workspace: ^1.0.0",
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-inject": "^5.0.3",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
@@ -54,6 +55,7 @@
|
||||
"@rollup/plugin-wasm": "^6.1.1",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"eslint": "^8.10.0",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"@mui/styles": "^5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@nymproject/eslint-config-react-typescript": "workspace: jnshhbwshsbsjZ hb ^1.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"eslint": "^8.10.0",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"start": "tsc -w",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'yarn build:dev:esm'",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'pnpm build:dev:esm'",
|
||||
"test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js -c=jest.config.mjs --no-cache",
|
||||
"tsc": "tsc --noEmit true"
|
||||
},
|
||||
@@ -34,7 +34,7 @@
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@nymproject/eslint-config-react-typescript": "workspace: ^1.0.0",
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-inject": "^5.0.3",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
"docs:generate:prod": "typedoc --basePath ./docs/tsdoc/nymproject/sdk/",
|
||||
"docs:prod:build": "scripts/build-prod-docs-collect.sh",
|
||||
"docs:serve": "reload -b -d ./docs -p 3000",
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"yarn docs:generate\"",
|
||||
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"pnpm docs:generate\"",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"start": "tsc -w",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'yarn build:dev:esm'",
|
||||
"start:dev": "nodemon --watch src -e ts,json --exec 'pnpm build:dev:esm'",
|
||||
"tsc": "tsc --noEmit true"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -40,6 +40,7 @@
|
||||
"@rollup/plugin-url": "^8.0.1",
|
||||
"@rollup/plugin-wasm": "^6.1.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"eslint": "^8.10.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,21 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json"
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"],
|
||||
"ignorePatterns": ["dist", ".eslintrc.cjs"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["react-refresh"],
|
||||
"rules": {
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{
|
||||
"allowConstantExport": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"react-hooks/exhaustive-deps": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1,40 +1,25 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
export const framework = {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: '@storybook/react',
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
},
|
||||
// webpackFinal: async (config, { configType }) => {
|
||||
// // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
||||
// // You can change the configuration based on that.
|
||||
// // 'PRODUCTION' is used when building the static version of storybook.
|
||||
webpackFinal: async (config) => {
|
||||
config.module.rules.forEach((rule) => {
|
||||
// look for SVG import rule and replace
|
||||
// NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
|
||||
if (rule.test?.toString().includes('svg')) {
|
||||
rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
|
||||
}
|
||||
});
|
||||
export const docs = {
|
||||
autodocs: true,
|
||||
};
|
||||
|
||||
// handle asset loading with this
|
||||
config.module.rules.unshift({
|
||||
test: /\.svg(\?.*)?$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
export const typescript = {
|
||||
reactDocgen: 'react-docgen-typescript',
|
||||
};
|
||||
|
||||
config.resolve.extensions = ['.tsx', '.ts', '.js'];
|
||||
config.resolve.plugins = [new TsconfigPathsPlugin()];
|
||||
const config = {
|
||||
framework: '@storybook/react-vite',
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
|
||||
// Return the altered config
|
||||
return config;
|
||||
},
|
||||
features: {
|
||||
emotionAlias: false,
|
||||
docs: {},
|
||||
|
||||
typescript: {
|
||||
reactDocgen: 'react-docgen-typescript',
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,58 +1,30 @@
|
||||
# Nym Shared React Components
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This package contains shared React components that are used in other Nym projects.
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
It uses the following packages:
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [shared MUI theme](../mui-theme/README.md)
|
||||
- [webpack config](../webpack/README.md)
|
||||
- [MUI](https://https://mui.com/)
|
||||
- Typescript
|
||||
- React
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Building
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
```
|
||||
yarn
|
||||
yarn build
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default {
|
||||
// other rules...
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Run watch mode with:
|
||||
|
||||
```
|
||||
yarn watch
|
||||
```
|
||||
|
||||
Or you can run Storybook with:
|
||||
|
||||
```
|
||||
yarn storybook
|
||||
```
|
||||
|
||||
Or you can run the [example project](../react-webpack-with-theme-example/README.md) in dev mode and this package in watch mode, and test results in the example project's dev server output.
|
||||
|
||||
## Playground
|
||||
|
||||
There are [playground components](./src/playground/index.tsx) that are intended to be used during development to see the effects are changes to the MUI theme or shared components at a glance.
|
||||
|
||||
They are available in Storybook from [src/stories/Playground.stories.tsx](./src/stories/Playground.stories.tsx).
|
||||
|
||||
> ℹ️ **Tip**: use the playground to make sure components stay consistent and you don't break other components while making changes
|
||||
|
||||
## Shared assets
|
||||
|
||||
This project uses [shared asset files](../../assets/README.md) such as favicons and logos.
|
||||
|
||||
> ℹ️ **Tip**: use to keep your project consistent with Nym's branding and so that it automatically receives changes when the shared assets change. Please try to avoid duplicating the files in the shared assets directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
This package is not published to NPM ... yet.
|
||||
|
||||
## TODO
|
||||
|
||||
- ugprade to React 18
|
||||
- upgrade Storybook
|
||||
- upgrade MUI
|
||||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Nym React Components</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
+6
-5
@@ -1,12 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { InputAdornment, TextField } from '@mui/material';
|
||||
import { InputAdornment, TextField, SxProps } from '@mui/material';
|
||||
import { TextFieldProps } from '@mui/material/TextField/TextField';
|
||||
import { validateWalletAddress } from '@nymproject/types';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
import { SxProps } from '@mui/system';
|
||||
|
||||
export const WalletAddressFormField: FCWithChildren<{
|
||||
export type WalletAddressFormFieldProps = {
|
||||
showTickOnValid?: boolean;
|
||||
fullWidth?: boolean;
|
||||
required?: boolean;
|
||||
@@ -20,7 +19,9 @@ export const WalletAddressFormField: FCWithChildren<{
|
||||
textFieldProps?: TextFieldProps;
|
||||
errorText?: string;
|
||||
sx?: SxProps;
|
||||
}> = ({
|
||||
};
|
||||
|
||||
export const WalletAddressFormField = ({
|
||||
required,
|
||||
fullWidth,
|
||||
placeholder,
|
||||
@@ -33,7 +34,7 @@ export const WalletAddressFormField: FCWithChildren<{
|
||||
onValidate,
|
||||
textFieldProps,
|
||||
showTickOnValid = true,
|
||||
}) => {
|
||||
}: WalletAddressFormFieldProps) => {
|
||||
const [value, setValue] = React.useState<string | undefined>(initialValue);
|
||||
const [validationError, setValidationError] = React.useState<string | undefined>();
|
||||
|
||||
+3
-4
@@ -1,14 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Collapse, Alert, IconButton, Typography, Divider } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { SxProps } from '@mui/material';
|
||||
|
||||
export interface BannerProps {
|
||||
export type BannerProps = {
|
||||
open: boolean;
|
||||
onClick: () => void;
|
||||
height?: number;
|
||||
sx?: SxProps;
|
||||
}
|
||||
};
|
||||
|
||||
export const MaintenanceBanner = (props: BannerProps) => {
|
||||
const { open, onClick, height, sx } = props;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user