From b27fa51092f7464a1ef40e616a13c577b14b77e6 Mon Sep 17 00:00:00 2001 From: Fouad Date: Fri, 7 Jul 2023 11:02:05 +0100 Subject: [PATCH] Feature/nym browser extension (#3637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Chore/browser extension bootstrap (#3257) * init package * set up TS and Webpack * add eslint config * add prettier config * add react and mui theme * add CI * update mui theme version number * Chore/browser extension routes (#3327) * start routes * create layouts * add initial app routes * add initial app pages * add global types * create reuseable components * move password and mnemonic fields to shared react components package * refactor register routes * move client address component to shared package * move components to ui folder * create menu and appbar components * adjust layout components * add readme * use memory router * Feature/nym browser extension login and send (#3373) * init package * set up TS and Webpack * add eslint config * add prettier config * add react and mui theme * add CI * update mui theme version number * Chore/browser extension routes (#3327) * start routes * create layouts * add initial app routes * add initial app pages * add global types * create reuseable components * move password and mnemonic fields to shared react components package * refactor register routes * move client address component to shared package * move components to ui folder * create menu and appbar components * adjust layout components * add readme * use memory router * add extension to mono-repo config * fix webpack build * util functions * add TX type * refactor routes * refactor pages + add send page * add page layout for app pages * set up app context * app components * set up connection config * fix lint errors * Chore/browser extension bootstrap (#3257) * init package * set up TS and Webpack * add eslint config * add prettier config * add react and mui theme * add CI * update mui theme version number * Chore/browser extension routes (#3327) * start routes * create layouts * add initial app routes * add initial app pages * add global types * create reuseable components * move password and mnemonic fields to shared react components package * refactor register routes * move client address component to shared package * move components to ui folder * create menu and appbar components * adjust layout components * add readme * use memory router * add extension to mono-repo config * util functions * add TX type * refactor routes * refactor pages + add send page * add page layout for app pages * set up app context * app components * set up connection config * use fee simulation when sending tokens * use object argument for simulate send api * login validation + fee refinements * use components from shared components lib * add receive modal (#3408) * account storage via wasm * method to get all storage keys * Feature/nym browser extension password encryption (single account) (#3442) * build wasm * reuse components and state for password pages * refactor registration pages * use login with password * import storage as local package * add yarn preinstall script to ts lint gh action * install wasm-pack for CI * use @nym scope for ext storage package * introduced a call to check if database was already initialised (#3465) * introduced a call to check if database was already initialised * use extension storage method to check for db existance --------- Co-authored-by: fmtabbara * introduced mnemonic key existence check (#3462) * Browser extension - Multi-accounts + view mnemonic action (#3488) * add UI for multi-accounts + add view mnemonic for accounts * refactor routes * set up import account * add account to existing wallet * check if account name exists before creating new one * handle password errors * add token to currency conversion * fixed ClientStorageError import path * fix CI * fix CI --------- Co-authored-by: Jędrzej Stuczyński --- .github/workflows/typescript-lint.yml | 114 +-- Cargo.toml | 2 +- assets/logo/logo-bw.svg | 53 ++ clients/validator/package.json | 37 +- clients/validator/rollup.config.mjs | 8 +- clients/validator/scripts/build-prod.sh | 2 +- .../validator/scripts/buildPackageJson.mjs | 2 +- clients/validator/src/index.ts | 15 +- .../src/tests/simulate/simulateTx.test.ts | 9 +- clients/validator/tsconfig.json | 2 +- clients/webassembly/src/error.rs | 2 +- .../src/storage/{errors.rs => error.rs} | 0 clients/webassembly/src/storage/mod.rs | 4 +- clients/webassembly/src/storage/traits.rs | 2 +- common/wasm-utils/src/storage/error.rs | 3 + common/wasm-utils/src/storage/mod.rs | 83 ++ explorer/webpack.common.js | 4 +- nym-browser-extension/.eslintrc | 8 + nym-browser-extension/.gitignore | 5 + nym-browser-extension/.nvmrc | 1 + nym-browser-extension/.prettierrc | 6 + nym-browser-extension/.sample.env | 7 + nym-browser-extension/CHANGELOG.md | 0 nym-browser-extension/README.md | 40 + nym-browser-extension/package.json | 88 ++ nym-browser-extension/src/App.tsx | 15 + .../src/components/accounts/Accounts.tsx | 47 + .../src/components/accounts/Actions.tsx | 55 ++ .../components/accounts/ViewSeedPhrase.tsx | 68 ++ .../src/components/accounts/index.tsx | 3 + .../src/components/address/index.tsx | 16 + .../src/components/balance/index.tsx | 23 + nym-browser-extension/src/components/index.ts | 6 + .../src/components/receive/ReceiveModal.tsx | 35 + .../src/components/receive/index.ts | 1 + .../components/send/SendConfirmationModal.tsx | 30 + .../src/components/send/index.ts | 1 + .../src/components/ui/AppBar/index.tsx | 14 + .../src/components/ui/BackButton/index.tsx | 23 + .../src/components/ui/Button/index.tsx | 6 + .../src/components/ui/Logo/index.tsx | 6 + .../src/components/ui/LogoWithText/index.tsx | 20 + .../src/components/ui/MenuDrawer/index.tsx | 55 ++ .../src/components/ui/Modal/ErrorModal.tsx | 74 ++ .../src/components/ui/Modal/LoadingModal.tsx | 25 + .../src/components/ui/Modal/Modal.tsx | 71 ++ .../src/components/ui/Modal/index.tsx | 3 + .../src/components/ui/Title/index.tsx | 10 + .../src/components/ui/index.ts | 8 + nym-browser-extension/src/config.ts | 8 + nym-browser-extension/src/context/app.tsx | 109 +++ nym-browser-extension/src/context/index.tsx | 3 + .../src/context/register.tsx | 71 ++ nym-browser-extension/src/context/send.tsx | 112 +++ .../src/hooks/useCreatePassword.tsx | 21 + nym-browser-extension/src/hooks/useGetFee.ts | 40 + nym-browser-extension/src/index.html | 12 + nym-browser-extension/src/index.tsx | 10 + .../src/layouts/AppLayout.tsx | 8 + .../src/layouts/CenteredLogo.tsx | 29 + .../src/layouts/PageLayout.tsx | 38 + nym-browser-extension/src/layouts/TopLogo.tsx | 37 + nym-browser-extension/src/layouts/index.ts | 3 + nym-browser-extension/src/manifest.json | 18 + .../src/pages/accounts/Accounts.tsx | 43 + .../src/pages/accounts/AddAccount.tsx | 16 + .../src/pages/accounts/Complete.tsx | 14 + .../src/pages/accounts/ConfirmPassword.tsx | 43 + .../src/pages/accounts/ImportAccount.tsx | 17 + .../src/pages/accounts/NameAccount.tsx | 47 + .../src/pages/accounts/index.ts | 6 + .../src/pages/auth/ForgotPassword.tsx | 24 + .../src/pages/auth/Login.tsx | 69 ++ .../src/pages/auth/index.tsx | 2 + .../src/pages/auth/validationSchema.ts | 5 + .../src/pages/balance/index.tsx | 59 ++ .../src/pages/delegation/index.tsx | 3 + .../src/pages/home/index.tsx | 25 + nym-browser-extension/src/pages/index.ts | 8 + .../src/pages/receive/index.tsx | 3 + .../src/pages/register/Complete.tsx | 10 + .../CreatePasswordOnExistingAccount.tsx | 16 + .../register/CreatePasswordOnNewAccount.tsx | 16 + .../src/pages/register/ImportAccount.tsx | 19 + .../src/pages/register/SeedPhrase.tsx | 17 + .../src/pages/register/index.ts | 5 + .../src/pages/send/Confirmation.tsx | 68 ++ .../src/pages/send/index.tsx | 80 ++ .../src/pages/settings/index.tsx | 3 + .../src/pages/templates/Complete.tsx | 26 + .../src/pages/templates/CreatePassword.tsx | 63 ++ .../src/pages/templates/ImportAccount.tsx | 45 + .../src/pages/templates/SeedPhrase.tsx | 60 ++ .../src/pages/templates/index.ts | 4 + nym-browser-extension/src/routes/index.tsx | 35 + .../src/routes/login/index.tsx | 26 + .../src/routes/register/index.tsx | 38 + .../src/routes/user/accounts/accounts.tsx | 17 + .../src/routes/user/index.tsx | 25 + .../src/theme/NymBrowserExtensionTheme.tsx | 18 + .../src/theme/mui-theme.d.ts | 66 ++ nym-browser-extension/src/theme/theme.ts | 138 +++ nym-browser-extension/src/types/global.ts | 5 + nym-browser-extension/src/types/index.ts | 1 + nym-browser-extension/src/types/tx.ts | 9 + nym-browser-extension/src/typings/jpeg.d.ts | 4 + nym-browser-extension/src/typings/json.d.ts | 4 + nym-browser-extension/src/typings/png.d.ts | 4 + nym-browser-extension/src/typings/svg.d.ts | 4 + nym-browser-extension/src/urls/index.ts | 2 + nym-browser-extension/src/utils/coin.ts | 21 + nym-browser-extension/src/utils/crypto.ts | 8 + nym-browser-extension/src/utils/fee.ts | 8 + nym-browser-extension/src/utils/price.ts | 18 + .../src/validator-client/index.tsx | 15 + .../storage/.cargo/config.toml | 2 + nym-browser-extension/storage/.gitignore | 2 + nym-browser-extension/storage/Cargo.lock | 814 ++++++++++++++++++ nym-browser-extension/storage/Cargo.toml | 39 + nym-browser-extension/storage/Makefile | 2 + .../storage/internal-dev/index.js | 73 ++ nym-browser-extension/storage/src/error.rs | 37 + nym-browser-extension/storage/src/lib.rs | 22 + nym-browser-extension/storage/src/storage.rs | 182 ++++ nym-browser-extension/tsconfig.eslint.json | 17 + nym-browser-extension/tsconfig.json | 32 + nym-browser-extension/webpack.common.js | 26 + nym-browser-extension/webpack.dev.js | 68 ++ nym-browser-extension/webpack.prod.js | 21 + .../Accounts/modals/AddAccountModal.tsx | 2 +- .../Accounts/modals/MnemonicModal.tsx | 3 +- nym-wallet/src/components/Buy/Tutorial.tsx | 10 +- nym-wallet/src/components/ConfirmPassword.tsx | 2 +- .../components/Delegation/DelegationModal.tsx | 3 - .../src/components/Receive/ReceiveModal.tsx | 11 +- .../Settings/PasswordCreateForm.tsx | 3 +- .../Settings/PasswordUpdateForm.tsx | 2 +- nym-wallet/src/components/index.ts | 2 - .../src/pages/auth/pages/confirm-mnemonic.tsx | 2 +- .../src/pages/auth/pages/connect-password.tsx | 2 +- .../src/pages/auth/pages/create-password.tsx | 2 +- .../src/pages/auth/pages/signin-mnemonic.tsx | 2 +- .../src/pages/auth/pages/signin-password.tsx | 2 +- nym-wallet/src/pages/balance/Balance.tsx | 10 +- package.json | 6 +- .../yarn/storage-placeholder/package.json | 5 + ts-packages/mui-theme/package.json | 4 +- ts-packages/react-components/package.json | 1 + .../client-address}/ClientAddress.stories.tsx | 8 + .../client-address}/ClientAddress.tsx | 24 +- .../components/clipboard/CopyToClipboard.tsx | 11 +- .../src/components/logo/NymLogoBW.tsx | 5 + .../password-strength/PasswordStrength.tsx | 95 ++ .../src/components/textfields/Mnemonic.tsx | 37 + .../src/components/textfields/Password.tsx | 36 +- .../src/components/warnings/Error.tsx | 13 + yarn.lock | 404 ++++++++- 157 files changed, 4766 insertions(+), 191 deletions(-) create mode 100644 assets/logo/logo-bw.svg rename clients/webassembly/src/storage/{errors.rs => error.rs} (100%) create mode 100644 nym-browser-extension/.eslintrc create mode 100644 nym-browser-extension/.gitignore create mode 100644 nym-browser-extension/.nvmrc create mode 100644 nym-browser-extension/.prettierrc create mode 100644 nym-browser-extension/.sample.env create mode 100644 nym-browser-extension/CHANGELOG.md create mode 100644 nym-browser-extension/README.md create mode 100644 nym-browser-extension/package.json create mode 100644 nym-browser-extension/src/App.tsx create mode 100644 nym-browser-extension/src/components/accounts/Accounts.tsx create mode 100644 nym-browser-extension/src/components/accounts/Actions.tsx create mode 100644 nym-browser-extension/src/components/accounts/ViewSeedPhrase.tsx create mode 100644 nym-browser-extension/src/components/accounts/index.tsx create mode 100644 nym-browser-extension/src/components/address/index.tsx create mode 100644 nym-browser-extension/src/components/balance/index.tsx create mode 100644 nym-browser-extension/src/components/index.ts create mode 100644 nym-browser-extension/src/components/receive/ReceiveModal.tsx create mode 100644 nym-browser-extension/src/components/receive/index.ts create mode 100644 nym-browser-extension/src/components/send/SendConfirmationModal.tsx create mode 100644 nym-browser-extension/src/components/send/index.ts create mode 100644 nym-browser-extension/src/components/ui/AppBar/index.tsx create mode 100644 nym-browser-extension/src/components/ui/BackButton/index.tsx create mode 100644 nym-browser-extension/src/components/ui/Button/index.tsx create mode 100644 nym-browser-extension/src/components/ui/Logo/index.tsx create mode 100644 nym-browser-extension/src/components/ui/LogoWithText/index.tsx create mode 100644 nym-browser-extension/src/components/ui/MenuDrawer/index.tsx create mode 100644 nym-browser-extension/src/components/ui/Modal/ErrorModal.tsx create mode 100644 nym-browser-extension/src/components/ui/Modal/LoadingModal.tsx create mode 100644 nym-browser-extension/src/components/ui/Modal/Modal.tsx create mode 100644 nym-browser-extension/src/components/ui/Modal/index.tsx create mode 100644 nym-browser-extension/src/components/ui/Title/index.tsx create mode 100644 nym-browser-extension/src/components/ui/index.ts create mode 100644 nym-browser-extension/src/config.ts create mode 100644 nym-browser-extension/src/context/app.tsx create mode 100644 nym-browser-extension/src/context/index.tsx create mode 100644 nym-browser-extension/src/context/register.tsx create mode 100644 nym-browser-extension/src/context/send.tsx create mode 100644 nym-browser-extension/src/hooks/useCreatePassword.tsx create mode 100644 nym-browser-extension/src/hooks/useGetFee.ts create mode 100644 nym-browser-extension/src/index.html create mode 100644 nym-browser-extension/src/index.tsx create mode 100644 nym-browser-extension/src/layouts/AppLayout.tsx create mode 100644 nym-browser-extension/src/layouts/CenteredLogo.tsx create mode 100644 nym-browser-extension/src/layouts/PageLayout.tsx create mode 100644 nym-browser-extension/src/layouts/TopLogo.tsx create mode 100644 nym-browser-extension/src/layouts/index.ts create mode 100644 nym-browser-extension/src/manifest.json create mode 100644 nym-browser-extension/src/pages/accounts/Accounts.tsx create mode 100644 nym-browser-extension/src/pages/accounts/AddAccount.tsx create mode 100644 nym-browser-extension/src/pages/accounts/Complete.tsx create mode 100644 nym-browser-extension/src/pages/accounts/ConfirmPassword.tsx create mode 100644 nym-browser-extension/src/pages/accounts/ImportAccount.tsx create mode 100644 nym-browser-extension/src/pages/accounts/NameAccount.tsx create mode 100644 nym-browser-extension/src/pages/accounts/index.ts create mode 100644 nym-browser-extension/src/pages/auth/ForgotPassword.tsx create mode 100644 nym-browser-extension/src/pages/auth/Login.tsx create mode 100644 nym-browser-extension/src/pages/auth/index.tsx create mode 100644 nym-browser-extension/src/pages/auth/validationSchema.ts create mode 100644 nym-browser-extension/src/pages/balance/index.tsx create mode 100644 nym-browser-extension/src/pages/delegation/index.tsx create mode 100644 nym-browser-extension/src/pages/home/index.tsx create mode 100644 nym-browser-extension/src/pages/index.ts create mode 100644 nym-browser-extension/src/pages/receive/index.tsx create mode 100644 nym-browser-extension/src/pages/register/Complete.tsx create mode 100644 nym-browser-extension/src/pages/register/CreatePasswordOnExistingAccount.tsx create mode 100644 nym-browser-extension/src/pages/register/CreatePasswordOnNewAccount.tsx create mode 100644 nym-browser-extension/src/pages/register/ImportAccount.tsx create mode 100644 nym-browser-extension/src/pages/register/SeedPhrase.tsx create mode 100644 nym-browser-extension/src/pages/register/index.ts create mode 100644 nym-browser-extension/src/pages/send/Confirmation.tsx create mode 100644 nym-browser-extension/src/pages/send/index.tsx create mode 100644 nym-browser-extension/src/pages/settings/index.tsx create mode 100644 nym-browser-extension/src/pages/templates/Complete.tsx create mode 100644 nym-browser-extension/src/pages/templates/CreatePassword.tsx create mode 100644 nym-browser-extension/src/pages/templates/ImportAccount.tsx create mode 100644 nym-browser-extension/src/pages/templates/SeedPhrase.tsx create mode 100644 nym-browser-extension/src/pages/templates/index.ts create mode 100644 nym-browser-extension/src/routes/index.tsx create mode 100644 nym-browser-extension/src/routes/login/index.tsx create mode 100644 nym-browser-extension/src/routes/register/index.tsx create mode 100644 nym-browser-extension/src/routes/user/accounts/accounts.tsx create mode 100644 nym-browser-extension/src/routes/user/index.tsx create mode 100644 nym-browser-extension/src/theme/NymBrowserExtensionTheme.tsx create mode 100644 nym-browser-extension/src/theme/mui-theme.d.ts create mode 100644 nym-browser-extension/src/theme/theme.ts create mode 100644 nym-browser-extension/src/types/global.ts create mode 100644 nym-browser-extension/src/types/index.ts create mode 100644 nym-browser-extension/src/types/tx.ts create mode 100644 nym-browser-extension/src/typings/jpeg.d.ts create mode 100644 nym-browser-extension/src/typings/json.d.ts create mode 100644 nym-browser-extension/src/typings/png.d.ts create mode 100644 nym-browser-extension/src/typings/svg.d.ts create mode 100644 nym-browser-extension/src/urls/index.ts create mode 100644 nym-browser-extension/src/utils/coin.ts create mode 100644 nym-browser-extension/src/utils/crypto.ts create mode 100644 nym-browser-extension/src/utils/fee.ts create mode 100644 nym-browser-extension/src/utils/price.ts create mode 100644 nym-browser-extension/src/validator-client/index.tsx create mode 100644 nym-browser-extension/storage/.cargo/config.toml create mode 100644 nym-browser-extension/storage/.gitignore create mode 100644 nym-browser-extension/storage/Cargo.lock create mode 100644 nym-browser-extension/storage/Cargo.toml create mode 100644 nym-browser-extension/storage/Makefile create mode 100644 nym-browser-extension/storage/internal-dev/index.js create mode 100644 nym-browser-extension/storage/src/error.rs create mode 100644 nym-browser-extension/storage/src/lib.rs create mode 100644 nym-browser-extension/storage/src/storage.rs create mode 100644 nym-browser-extension/tsconfig.eslint.json create mode 100644 nym-browser-extension/tsconfig.json create mode 100644 nym-browser-extension/webpack.common.js create mode 100644 nym-browser-extension/webpack.dev.js create mode 100644 nym-browser-extension/webpack.prod.js create mode 100644 scripts/build/yarn/storage-placeholder/package.json rename {nym-wallet/src/components => ts-packages/react-components/src/components/client-address}/ClientAddress.stories.tsx (86%) rename {nym-wallet/src/components => ts-packages/react-components/src/components/client-address}/ClientAddress.tsx (60%) create mode 100644 ts-packages/react-components/src/components/logo/NymLogoBW.tsx create mode 100644 ts-packages/react-components/src/components/password-strength/PasswordStrength.tsx create mode 100644 ts-packages/react-components/src/components/textfields/Mnemonic.tsx rename nym-wallet/src/components/textfields.tsx => ts-packages/react-components/src/components/textfields/Password.tsx (53%) create mode 100644 ts-packages/react-components/src/components/warnings/Error.tsx diff --git a/.github/workflows/typescript-lint.yml b/.github/workflows/typescript-lint.yml index 829ffb22e9..dcaa905f5d 100644 --- a/.github/workflows/typescript-lint.yml +++ b/.github/workflows/typescript-lint.yml @@ -3,64 +3,70 @@ name: CI for linting Typescript on: push: paths: - - 'ts-packages/**' - - 'sdk/typescript/**' - - 'nym-connect/desktop/src/**' - - 'nym-connect/desktop/package.json' - - 'nym-connect/mobile/src/**' - - 'nym-connect/mobile/package.json' - - 'nym-wallet/src/**' - - 'nym-wallet/package.json' - - 'explorer/**' + - "ts-packages/**" + - "sdk/typescript/**" + - "nym-connect/desktop/src/**" + - "nym-connect/desktop/package.json" + - "nym-connect/mobile/src/**" + - "nym-connect/mobile/package.json" + - "nym-wallet/src/**" + - "nym-wallet/package.json" + - "explorer/**" pull_request: paths: - - 'ts-packages/**' - - 'sdk/typescript/**' - - 'nym-connect/desktop/src/**' - - 'nym-connect/desktop/package.json' - - 'nym-connect/mobile/src/**' - - 'nym-connect/mobile/package.json' - - 'nym-wallet/src/**' - - 'nym-wallet/package.json' - - 'explorer/**' + - "ts-packages/**" + - "sdk/typescript/**" + - "nym-connect/desktop/src/**" + - "nym-connect/desktop/package.json" + - "nym-connect/mobile/src/**" + - "nym-connect/mobile/package.json" + - "nym-wallet/src/**" + - "nym-wallet/package.json" + - "explorer/**" jobs: build: runs-on: custom-runner-linux steps: - - uses: actions/checkout@v2 - - name: Install rsync - run: sudo apt-get install rsync - continue-on-error: true - - uses: rlespinasse/github-slug-action@v3.x - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Setup yarn - run: npm install -g yarn - - name: Install - run: yarn - - name: Build packages - run: yarn build - - name: Lint - run: yarn lint && yarn tsc - - 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: 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: Setup yarn + run: npm install -g yarn + - name: Install + run: yarn + - name: Build packages + run: yarn build + - name: Lint + run: yarn lint && yarn tsc + - 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 diff --git a/Cargo.toml b/Cargo.toml index 9b2a13c7ef..4f0e80d599 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ default-members = [ "explorer-api", ] -exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "cpu-cycles"] +exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-browser-extension/storage", "cpu-cycles"] [workspace.package] authors = ["Nym Technologies SA"] diff --git a/assets/logo/logo-bw.svg b/assets/logo/logo-bw.svg new file mode 100644 index 0000000000..42b752d8d0 --- /dev/null +++ b/assets/logo/logo-bw.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + diff --git a/clients/validator/package.json b/clients/validator/package.json index 8887605fe7..386a94fe23 100644 --- a/clients/validator/package.json +++ b/clients/validator/package.json @@ -3,11 +3,11 @@ "version": "0.19.0", "description": "A TypeScript client for interacting with smart contracts in Nym validators", "repository": "https://github.com/nymtech/nym", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "build": "rollup -c ./rollup.config.mjs", - "build:types": "rollup-type-bundler --dist ./dist/nym-validator-client", + "build:types": "rollup-type-bundler --dist ./dist", "build:prod": "sh ./scripts/build-prod.sh", "test": "ts-mocha -p ./tsconfig.test.json ./src/tests/**/*.test.ts", "testmock": "ts-mocha -p ./tsconfig.test.json ./src/tests/mock/*.test.ts", @@ -29,16 +29,23 @@ ], "license": "Apache-2.0", "devDependencies": { + "@cosmjs/cosmwasm-stargate": "^0.29.5", + "@cosmjs/crypto": "^0.29.5", + "@cosmjs/math": "^0.29.5", + "@cosmjs/proto-signing": "^0.29.5", + "@cosmjs/stargate": "^0.29.5", + "@cosmjs/tendermint-rpc": "^0.29.5", "@favware/rollup-type-bundler": "^2.0.0", "@nymproject/types": "^1.0.0", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-node-resolve": "^15.0.1", - "rollup": "^3.17.2", - "rollup-plugin-dts": "^5.2.0", + "@rollup/plugin-typescript": "^11.0.0", "@typescript-eslint/eslint-plugin": "^5.7.0", "@typescript-eslint/parser": "^5.7.0", + "axios": "^1.3.3", + "cosmjs-types": "^0.4.1", + "dotenv": "^16.0.3", "eslint": "^7.18.0", "eslint-config-airbnb": "^19.0.2", "eslint-config-airbnb-typescript": "^16.1.0", @@ -47,21 +54,15 @@ "eslint-plugin-import": "^2.25.4", "eslint-plugin-mocha": "^10.0.3", "eslint-plugin-prettier": "^4.0.0", + "expect": "^28.1.3", "mocha": "^10.0.0", + "moq.ts": "^7.3.4", "prettier": "^2.8.7", + "rollup": "^3.17.2", + "rollup-plugin-dts": "^5.2.0", + "rollup-plugin-node-polyfills": "^0.2.1", "ts-mocha": "^10.0.0", "typedoc": "^0.22.13", - "typescript": "^4.6.2", - "cosmjs-types": "^0.4.1", - "dotenv": "^16.0.3", - "expect": "^28.1.3", - "moq.ts": "^7.3.4", - "@cosmjs/cosmwasm-stargate": "^0.29.5", - "@cosmjs/crypto": "^0.29.5", - "@cosmjs/math": "^0.29.5", - "@cosmjs/proto-signing": "^0.29.5", - "@cosmjs/stargate": "^0.29.5", - "@cosmjs/tendermint-rpc": "^0.29.5", - "axios": "^1.3.3" + "typescript": "^4.6.2" } } diff --git a/clients/validator/rollup.config.mjs b/clients/validator/rollup.config.mjs index f78cf449cf..e97c06bee2 100644 --- a/clients/validator/rollup.config.mjs +++ b/clients/validator/rollup.config.mjs @@ -1,15 +1,15 @@ import typescript from '@rollup/plugin-typescript'; -import resolve from '@rollup/plugin-node-resolve'; +import nodePolyfills from 'rollup-plugin-node-polyfills'; import json from '@rollup/plugin-json'; import commonjs from '@rollup/plugin-commonjs'; export default [ { - input: './src/index.ts', + input: 'src/index.ts', output: { - dir: 'dist/nym-validator-client', + dir: 'dist', format: 'cjs', }, - plugins: [resolve(), typescript(), commonjs(), json()], + plugins: [nodePolyfills(), typescript(), commonjs(), json()], }, ]; diff --git a/clients/validator/scripts/build-prod.sh b/clients/validator/scripts/build-prod.sh index 06188652b0..7eb161d192 100644 --- a/clients/validator/scripts/build-prod.sh +++ b/clients/validator/scripts/build-prod.sh @@ -21,7 +21,7 @@ node ./scripts/buildPackageJson.mjs # Copy README -cp README.md dist/nym-validator-client +cp README.md dist/ # move the output outside of the yarn/npm workspaces diff --git a/clients/validator/scripts/buildPackageJson.mjs b/clients/validator/scripts/buildPackageJson.mjs index da95f10e9d..6da8d289a5 100644 --- a/clients/validator/scripts/buildPackageJson.mjs +++ b/clients/validator/scripts/buildPackageJson.mjs @@ -17,4 +17,4 @@ const packageJson = { types, }; -fs.writeFileSync('./dist/nym-validator-client/package.json', JSON.stringify(packageJson, null, 2)); +fs.writeFileSync('./dist/package.json', JSON.stringify(packageJson, null, 2)); diff --git a/clients/validator/src/index.ts b/clients/validator/src/index.ts index d09a202561..6f00d7f76a 100644 --- a/clients/validator/src/index.ts +++ b/clients/validator/src/index.ts @@ -45,7 +45,6 @@ import { } from '@nymproject/types'; import QueryClient from './query-client'; import SigningClient, { ISigningClient } from './signing-client'; -// import { DelegationBlock } from './types/shared'; export interface INymClient { readonly mixnetContract: string; @@ -626,7 +625,17 @@ export default class ValidatorClient implements INymClient { // SIMULATE - public async simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) { - return (this.client as SigningClient).simulateSend(signingAddress, from, to, amount); + public async simulateSend({ + signingAddress, + from, + to, + amount, + }: { + signingAddress: string; + from: string; + to: string; + amount: Coin[]; + }) { + return (this.client as ISigningClient).simulateSend(signingAddress, from, to, amount); } } diff --git a/clients/validator/src/tests/simulate/simulateTx.test.ts b/clients/validator/src/tests/simulate/simulateTx.test.ts index eefed5c8fb..cb6df7899e 100644 --- a/clients/validator/src/tests/simulate/simulateTx.test.ts +++ b/clients/validator/src/tests/simulate/simulateTx.test.ts @@ -22,9 +22,12 @@ describe('Simualtions', () => { }); it('can simulate sending tokens', async () => { - const res = await client.simulateSend(client.address, client.address, client.address, [ - { amount: '400000', denom: 'unym' }, - ]); + const res = await client.simulateSend({ + signingAddress: client.address, + from: client.address, + to: client.address, + amount: [{ amount: '400000', denom: 'unym' }], + }); expect(typeof res).toBe('number'); }).timeout(10000); diff --git a/clients/validator/tsconfig.json b/clients/validator/tsconfig.json index 0a2ce0f074..1249657c46 100644 --- a/clients/validator/tsconfig.json +++ b/clients/validator/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "outDir": "./dist/nym-validator-client", + "outDir": "dist", "module": "ES2020", "target": "es2021", "allowJs": false, diff --git a/clients/webassembly/src/error.rs b/clients/webassembly/src/error.rs index 0526e03ed4..9cf2369994 100644 --- a/clients/webassembly/src/error.rs +++ b/clients/webassembly/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::storage::errors::ClientStorageError; +use crate::storage::error::ClientStorageError; use crate::topology::WasmTopologyError; use js_sys::Promise; use nym_client_core::config::GatewayEndpointConfig; diff --git a/clients/webassembly/src/storage/errors.rs b/clients/webassembly/src/storage/error.rs similarity index 100% rename from clients/webassembly/src/storage/errors.rs rename to clients/webassembly/src/storage/error.rs diff --git a/clients/webassembly/src/storage/mod.rs b/clients/webassembly/src/storage/mod.rs index d13e27e6e9..f4ab912657 100644 --- a/clients/webassembly/src/storage/mod.rs +++ b/clients/webassembly/src/storage/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::config::Config; -use crate::storage::errors::ClientStorageError; +use crate::storage::error::ClientStorageError; use js_sys::Promise; use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails; use nym_crypto::asymmetric::{encryption, identity}; @@ -15,7 +15,7 @@ use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage}; use wasm_utils::PromisableResult; use zeroize::Zeroizing; -pub(crate) mod errors; +pub(crate) mod error; pub(crate) mod traits; const STORAGE_NAME_PREFIX: &str = "wasm-client-storage"; diff --git a/clients/webassembly/src/storage/traits.rs b/clients/webassembly/src/storage/traits.rs index ed56dda969..f4a4c35742 100644 --- a/clients/webassembly/src/storage/traits.rs +++ b/clients/webassembly/src/storage/traits.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::storage::errors::ClientStorageError; +use crate::storage::error::ClientStorageError; use crate::storage::ClientStorage; use async_trait::async_trait; use nym_client_core::client::base_client::storage::gateway_details::{ diff --git a/common/wasm-utils/src/storage/error.rs b/common/wasm-utils/src/storage/error.rs index 45b16a2d8e..9ead907c08 100644 --- a/common/wasm-utils/src/storage/error.rs +++ b/common/wasm-utils/src/storage/error.rs @@ -22,6 +22,9 @@ pub enum StorageError { message: String, }, + #[error("FATAL ERROR: storage key is somehow present {count} times in the table!")] + DuplicateKey { count: u32 }, + #[error("encountered issue with our storage encryption layer: {source}")] CryptoStorageError { #[from] diff --git a/common/wasm-utils/src/storage/mod.rs b/common/wasm-utils/src/storage/mod.rs index 3863c354fd..1a5b601ce6 100644 --- a/common/wasm-utils/src/storage/mod.rs +++ b/common/wasm-utils/src/storage/mod.rs @@ -4,6 +4,7 @@ use crate::console_log; use crate::storage::cipher_export::StoredExportedStoreCipher; use crate::storage::error::StorageError; +use futures::TryFutureExt; use indexed_db_futures::IdbDatabase; use nym_store_cipher::{ Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned, @@ -87,6 +88,23 @@ impl WasmStorage { }) } + pub async fn exists(db_name: &str) -> Result { + let db_req: OpenDbRequest = IdbDatabase::open(db_name)?; + let db: IdbDatabase = db_req.into_future().await?; + + // if the db was already created before, at the very least cipher info store should exist, + // thus the iterator should return at least one value + let some_stores_exist = db.object_store_names().next().is_some(); + + // that's super annoying - we have to do cleanup because opening db creates it + // (if it didn't exist before) + if !some_stores_exist { + db.delete()?.into_future().await? + } + + Ok(some_stores_exist) + } + pub fn serialize_value(&self, value: &T) -> Result { if let Some(cipher) = &self.store_cipher { let encrypted = cipher.encrypt_json_value(value)?; @@ -134,6 +152,35 @@ impl WasmStorage { .store_value_raw(store, key, &self.serialize_value(&value)?) .await } + + pub async fn remove_value(&self, store: &str, key: K) -> Result<(), StorageError> + where + K: wasm_bindgen::JsCast, + { + self.inner.remove_value_raw(store, key).await + } + + pub async fn has_value(&self, store: &str, key: K) -> Result + where + K: wasm_bindgen::JsCast, + { + match self.key_count(store, key).await? { + n if n == 0 => Ok(false), + n if n == 1 => Ok(true), + n => Err(StorageError::DuplicateKey { count: n }), + } + } + + pub async fn key_count(&self, store: &str, key: K) -> Result + where + K: wasm_bindgen::JsCast, + { + self.inner.get_key_count(store, key).await + } + + pub async fn get_all_keys(&self, store: &str) -> Result { + self.inner.get_all_keys(store).await + } } struct IdbWrapper(IdbDatabase); @@ -169,6 +216,42 @@ impl IdbWrapper { .map_err(Into::into) } + async fn remove_value_raw(&self, store: &str, key: K) -> Result<(), StorageError> + where + K: wasm_bindgen::JsCast, + { + self.0 + .transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)? + .object_store(store)? + .delete_owned(key)? + .into_future() + .await + .map_err(Into::into) + } + + async fn get_key_count(&self, store: &str, key: K) -> Result + where + K: wasm_bindgen::JsCast, + { + self.0 + .transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)? + .object_store(store)? + .count_with_key_owned(key)? + .into_future() + .await + .map_err(Into::into) + } + + async fn get_all_keys(&self, store: &str) -> Result { + self.0 + .transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)? + .object_store(store)? + .get_all_keys()? + .into_future() + .await + .map_err(Into::into) + } + async fn read_exported_cipher_store( &self, ) -> Result, StorageError> { diff --git a/explorer/webpack.common.js b/explorer/webpack.common.js index 2ddfb56dda..70744b13f2 100644 --- a/explorer/webpack.common.js +++ b/explorer/webpack.common.js @@ -26,6 +26,6 @@ module.exports = mergeWithRules({ crypto: false, net: false, zlib: false, - } - } + }, + }, }); diff --git a/nym-browser-extension/.eslintrc b/nym-browser-extension/.eslintrc new file mode 100644 index 0000000000..3ea1281af1 --- /dev/null +++ b/nym-browser-extension/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": [ + "@nymproject/eslint-config-react-typescript" + ], + "parserOptions": { + "project": "./tsconfig.eslint.json" + } +} diff --git a/nym-browser-extension/.gitignore b/nym-browser-extension/.gitignore new file mode 100644 index 0000000000..fdfd79ec62 --- /dev/null +++ b/nym-browser-extension/.gitignore @@ -0,0 +1,5 @@ +# environment +.env.dev + +# error logs +yarn-error.log diff --git a/nym-browser-extension/.nvmrc b/nym-browser-extension/.nvmrc new file mode 100644 index 0000000000..b6a7d89c68 --- /dev/null +++ b/nym-browser-extension/.nvmrc @@ -0,0 +1 @@ +16 diff --git a/nym-browser-extension/.prettierrc b/nym-browser-extension/.prettierrc new file mode 100644 index 0000000000..ccf75b89de --- /dev/null +++ b/nym-browser-extension/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 2 +} diff --git a/nym-browser-extension/.sample.env b/nym-browser-extension/.sample.env new file mode 100644 index 0000000000..85128503d8 --- /dev/null +++ b/nym-browser-extension/.sample.env @@ -0,0 +1,7 @@ +RPC_URL= +VALIDATOR_URL= +PREFIX= +MIXNET_CONTRACT_ADDRESS= +VESTING_CONTRACT_ADDRESS= +DENOM= +BLOCK_EXPLORER_URL= \ No newline at end of file diff --git a/nym-browser-extension/CHANGELOG.md b/nym-browser-extension/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nym-browser-extension/README.md b/nym-browser-extension/README.md new file mode 100644 index 0000000000..a6ba4b6a97 --- /dev/null +++ b/nym-browser-extension/README.md @@ -0,0 +1,40 @@ +# Nym Browser Extension + +The Nym browser extension lets you access your Nym wallet via the browser. + +## Getting started + +You will need: + +- NodeJS (use `nvm install` to automatically install the correct version) +- `npm` +- `yarn` + +> **Note**: This project is part of a mono repo, so you will need to build the shared packages before starting. And any time they change, you'll need to rebuild them. + +From the [root of the repository](../README.md) run the following to build shared packages: + +``` +yarn +yarn build +``` + +From the `nym-browser-extension` directory of the `nym` monorepo, run: + +`yarn dev` to run the extension in dev mode. + +You can then open a browser to http://localhost:9000 and start development. + +OR + +`yarn build` to build the extension. + +The extension will build to the `nym-browser-extension/dist` directory. + +## Load extension + +To load the extension into a Chrome browser + +- Go to `settings > extensions > manage extensions` +- Select `Load unpacked` +- Select the `nym-browser-extension/dist` diff --git a/nym-browser-extension/package.json b/nym-browser-extension/package.json new file mode 100644 index 0000000000..e9617eb933 --- /dev/null +++ b/nym-browser-extension/package.json @@ -0,0 +1,88 @@ +{ + "name": "@nym/browser-extension", + "version": "0.1.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "preinstall": "yarn build:wasm", + "dev": "yarn webpack serve --config webpack.dev.js", + "build": "yarn preinstall && webpack build --progress --config webpack.prod.js", + "build:wasm": "cd storage && make wasm-pack", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "lint:ts": "tsc --noEmit", + "storybook": "start-storybook -p 6006", + "storybook:build": "build-storybook" + }, + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.7.0", + "@hookform/resolvers": "^3.1.0", + "@mui/icons-material": "^5.11.11", + "@mui/material": "^5.11.15", + "@mui/system": "^5.11.15", + "@nymproject/mui-theme": "^1.0.0", + "@nymproject/nym-validator-client": "0.19.0", + "@nymproject/react": "^1.0.0", + "@nymproject/types": "^1.0.0", + "@nymproject/extension-storage": "file:./storage/pkg", + "@storybook/react": "^6.5.16", + "big.js": "^6.2.1", + "crypto-js": "^4.1.1", + "qrcode.react": "^3.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.3", + "react-hook-form": "^7.43.9", + "react-router-dom": "^6.9.0", + "zod": "^3.21.4" + }, + "devDependencies": { + "@nymproject/eslint-config-react-typescript": "^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": "^14.0.0", + "@types/big.js": "^6.1.6", + "@types/crypto-js": "4.1.1", + "@types/jest": "^27.0.1", + "@types/node": "^18.16.1", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10", + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.13.0", + "copy-webpack-plugin": "^11.0.0", + "dotenv-webpack": "^8.0.1", + "eslint": "^8.10.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^16.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-root-import": "^1.0.4", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.1.1", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.29.2", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-storybook": "^0.5.12", + "favicons-webpack-plugin": "^5.0.2", + "html-webpack-plugin": "^5.3.2", + "jest": "^27.1.0", + "mini-css-extract-plugin": "^2.2.2", + "prettier": "^2.8.7", + "react-refresh": "^0.14.0", + "react-refresh-typescript": "^2.0.8", + "style-loader": "^3.3.1", + "ts-jest": "^27.0.5", + "ts-loader": "^9.4.2", + "tsconfig-paths-webpack-plugin": "^3.5.2", + "typescript": "^4.6.2", + "url-loader": "^4.1.1", + "util": "^0.12.5", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1", + "webpack-dev-server": "^4.5.0", + "webpack-favicons": "^1.3.8", + "webpack-merge": "^5.8.0" + } +} diff --git a/nym-browser-extension/src/App.tsx b/nym-browser-extension/src/App.tsx new file mode 100644 index 0000000000..a66ff64a2a --- /dev/null +++ b/nym-browser-extension/src/App.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { NymBrowserExtThemeWithMode } from './theme/NymBrowserExtensionTheme'; +import { AppRoutes } from './routes'; +import { AppLayout } from './layouts/AppLayout'; +import { AppProvider } from './context'; + +export const App = () => ( + + + + + + + +); diff --git a/nym-browser-extension/src/components/accounts/Accounts.tsx b/nym-browser-extension/src/components/accounts/Accounts.tsx new file mode 100644 index 0000000000..d5bf72e3ce --- /dev/null +++ b/nym-browser-extension/src/components/accounts/Accounts.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Avatar, ListItem, ListItemAvatar, ListItemButton, ListItemText } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import { useAppContext } from 'src/context'; +import { AccountActions } from './Actions'; + +const AccountItem = ({ + accountName, + disabled, + onSelect, +}: { + accountName: string; + disabled: boolean; + onSelect: () => void; +}) => ( + } divider> + + + {accountName[0]} + + + + +); + +export const AccountList = () => { + const navigate = useNavigate(); + const { accounts, selectAccount, selectedAccount } = useAppContext(); + + const handleSelectAccount = async (accountName: string) => { + await selectAccount(accountName); + navigate('/user/balance'); + }; + + return ( + <> + {accounts.map((accountName) => ( + handleSelectAccount(accountName)} + /> + ))} + + ); +}; diff --git a/nym-browser-extension/src/components/accounts/Actions.tsx b/nym-browser-extension/src/components/accounts/Actions.tsx new file mode 100644 index 0000000000..84d37881c1 --- /dev/null +++ b/nym-browser-extension/src/components/accounts/Actions.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { IconButton, ListItemIcon, ListItemText, Menu, MenuItem } from '@mui/material'; +import { MoreVert, VisibilityOutlined } from '@mui/icons-material'; +import { useAppContext } from 'src/context'; + +type ActionType = { + title: string; + Icon: React.ReactNode; + onSelect: () => void; +}; + +const ActionItem = ({ action }: { action: ActionType }) => ( + + {action.Icon} + {action.title} + +); + +export const AccountActions = ({ accountName }: { accountName: string }) => { + const { setShowSeedForAccount } = useAppContext(); + + const [anchorEl, setAnchorEl] = React.useState(null); + + const open = Boolean(anchorEl); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + const actions: Array = [ + { + title: 'View seed phrase', + Icon: , + onSelect: () => { + setShowSeedForAccount(accountName); + }, + }, + ]; + + return ( + <> + + + + + {actions.map((action) => ( + + ))} + + + ); +}; diff --git a/nym-browser-extension/src/components/accounts/ViewSeedPhrase.tsx b/nym-browser-extension/src/components/accounts/ViewSeedPhrase.tsx new file mode 100644 index 0000000000..372c770934 --- /dev/null +++ b/nym-browser-extension/src/components/accounts/ViewSeedPhrase.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import { Box, Card, CardContent, Typography } from '@mui/material'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; +import { ExtensionStorage } from '@nymproject/extension-storage'; +import { Button, ConfirmationModal } from 'src/components/ui'; + +const ShowSeedButton = ({ handleShowSeedPhrase }: { handleShowSeedPhrase: () => void }) => ( + +); + +const DoneButton = ({ onDone }: { onDone: () => void }) => ( + +); + +const Seed = ({ seed }: { seed: string }) => ( + + + {seed} + + +); + +export const ViewSeedPhrase = ({ accountName, onDone }: { accountName: string; onDone: () => void }) => { + const [seed, setSeed] = useState(); + const [password, setPassword] = useState(''); + const [error, setError] = useState(); + + const handleShowSeedPhrase = async () => { + try { + const storage = await new ExtensionStorage(password); + const accountSeed = await storage.read_mnemonic(accountName); + setSeed(accountSeed); + } catch (e) { + setError('Could not retrieve seed phrase. Please check your password'); + } + }; + + return ( + : + } + > + {seed ? ( + + ) : ( + + { + setPassword(pw); + }} + /> + + )} + + ); +}; diff --git a/nym-browser-extension/src/components/accounts/index.tsx b/nym-browser-extension/src/components/accounts/index.tsx new file mode 100644 index 0000000000..4cc1e41842 --- /dev/null +++ b/nym-browser-extension/src/components/accounts/index.tsx @@ -0,0 +1,3 @@ +export * from './Accounts'; +export * from './Actions'; +export * from './ViewSeedPhrase'; diff --git a/nym-browser-extension/src/components/address/index.tsx b/nym-browser-extension/src/components/address/index.tsx new file mode 100644 index 0000000000..79230b74f6 --- /dev/null +++ b/nym-browser-extension/src/components/address/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Typography } from '@mui/material'; +import { Stack } from '@mui/system'; +import { ClientAddress } from '@nymproject/react/client-address/ClientAddress'; +import { useAppContext } from 'src/context'; + +export const Address = () => { + const { client } = useAppContext(); + + return ( + + Address + + + ); +}; diff --git a/nym-browser-extension/src/components/balance/index.tsx b/nym-browser-extension/src/components/balance/index.tsx new file mode 100644 index 0000000000..a3b0391c22 --- /dev/null +++ b/nym-browser-extension/src/components/balance/index.tsx @@ -0,0 +1,23 @@ +import React, { useEffect } from 'react'; +import { Stack, Typography } from '@mui/material'; +import { useAppContext } from 'src/context'; + +export const Balance = () => { + const { balance, fiatBalance, currency, getBalance } = useAppContext(); + + useEffect(() => { + getBalance(); + }, []); + + const fiat = fiatBalance ? `~ ${Intl.NumberFormat().format(fiatBalance)} ${currency.toUpperCase()}` : '-'; + + return ( + + Available + + {balance} NYM + + {fiat} + + ); +}; diff --git a/nym-browser-extension/src/components/index.ts b/nym-browser-extension/src/components/index.ts new file mode 100644 index 0000000000..6838fdfd2b --- /dev/null +++ b/nym-browser-extension/src/components/index.ts @@ -0,0 +1,6 @@ +export * from './accounts'; +export * from './address'; +export * from './balance'; +export * from './receive'; +export * from './send'; +export * from './ui'; diff --git a/nym-browser-extension/src/components/receive/ReceiveModal.tsx b/nym-browser-extension/src/components/receive/ReceiveModal.tsx new file mode 100644 index 0000000000..dc84aa2a33 --- /dev/null +++ b/nym-browser-extension/src/components/receive/ReceiveModal.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Card, CardContent, Dialog, DialogContent, DialogTitle, IconButton, Stack, Typography } from '@mui/material'; +import { QRCodeSVG } from 'qrcode.react'; +import { useAppContext } from 'src/context'; +import { ClientAddress } from '@nymproject/react/client-address/ClientAddress'; +import { Close } from '@mui/icons-material'; + +export const ReceiveModal = ({ open, onClose }: { open: boolean; onClose: () => void }) => { + const { client } = useAppContext(); + return ( + + + + Receive + + + + + + + + + + + + + + Your Nym address + + + + + + ); +}; diff --git a/nym-browser-extension/src/components/receive/index.ts b/nym-browser-extension/src/components/receive/index.ts new file mode 100644 index 0000000000..356034db06 --- /dev/null +++ b/nym-browser-extension/src/components/receive/index.ts @@ -0,0 +1 @@ +export * from './ReceiveModal'; diff --git a/nym-browser-extension/src/components/send/SendConfirmationModal.tsx b/nym-browser-extension/src/components/send/SendConfirmationModal.tsx new file mode 100644 index 0000000000..59acb9a1dc --- /dev/null +++ b/nym-browser-extension/src/components/send/SendConfirmationModal.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { ConfirmationModal, Button } from 'src/components/ui'; + +export const SendConfirmationModal = ({ + amount, + txUrl, + onConfirm, +}: { + amount: string; + txUrl: string; + onConfirm: () => void; +}) => ( + + Done + + } + > + + {amount} + + + +); diff --git a/nym-browser-extension/src/components/send/index.ts b/nym-browser-extension/src/components/send/index.ts new file mode 100644 index 0000000000..cb5dd64f44 --- /dev/null +++ b/nym-browser-extension/src/components/send/index.ts @@ -0,0 +1 @@ +export * from './SendConfirmationModal'; diff --git a/nym-browser-extension/src/components/ui/AppBar/index.tsx b/nym-browser-extension/src/components/ui/AppBar/index.tsx new file mode 100644 index 0000000000..b6543c2862 --- /dev/null +++ b/nym-browser-extension/src/components/ui/AppBar/index.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { AppBar as MUIAppBar } from '@mui/material/'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; + +export const AppBar = ({ Action }: { Action: React.ReactNode }) => ( + + + {Action} + + +); + +export default AppBar; diff --git a/nym-browser-extension/src/components/ui/BackButton/index.tsx b/nym-browser-extension/src/components/ui/BackButton/index.tsx new file mode 100644 index 0000000000..11594ab13e --- /dev/null +++ b/nym-browser-extension/src/components/ui/BackButton/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ArrowBackIosRounded } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; + +export const BackButton = ({ onBack }: { onBack?: () => void }) => { + const navigate = useNavigate(); + + const handleClick = () => { + if (onBack) { + onBack(); + } else { + navigate(-1); + } + return undefined; + }; + + return ( + + + + ); +}; diff --git a/nym-browser-extension/src/components/ui/Button/index.tsx b/nym-browser-extension/src/components/ui/Button/index.tsx new file mode 100644 index 0000000000..e44aca5c61 --- /dev/null +++ b/nym-browser-extension/src/components/ui/Button/index.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { Button as MUIButton, ButtonProps } from '@mui/material'; + +export const Button = (props: ButtonProps) => ( + +); diff --git a/nym-browser-extension/src/components/ui/Logo/index.tsx b/nym-browser-extension/src/components/ui/Logo/index.tsx new file mode 100644 index 0000000000..9b3d4ae513 --- /dev/null +++ b/nym-browser-extension/src/components/ui/Logo/index.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { NymLogoBW } from '@nymproject/react/logo/NymLogoBW'; + +export const Logo = ({ small }: { small?: boolean }) => ( + +); diff --git a/nym-browser-extension/src/components/ui/LogoWithText/index.tsx b/nym-browser-extension/src/components/ui/LogoWithText/index.tsx new file mode 100644 index 0000000000..661cd0766d --- /dev/null +++ b/nym-browser-extension/src/components/ui/LogoWithText/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Stack, Typography } from '@mui/material'; +import { Logo } from '../Logo'; +import { Title } from '../Title'; + +export const LogoWithText = ({ + logoSmall, + title, + description, +}: { + logoSmall?: boolean; + title: string; + description?: string; +}) => ( + + + {title} + {description} + +); diff --git a/nym-browser-extension/src/components/ui/MenuDrawer/index.tsx b/nym-browser-extension/src/components/ui/MenuDrawer/index.tsx new file mode 100644 index 0000000000..fcc0cafb95 --- /dev/null +++ b/nym-browser-extension/src/components/ui/MenuDrawer/index.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Drawer from '@mui/material/Drawer'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { AccountBalanceWalletRounded, AccountCircleRounded, ArrowDownwardRounded } from '@mui/icons-material'; +import { Link } from 'react-router-dom'; + +const menuSchema = [ + { + title: 'Accounts', + Icon: , + path: '/user/accounts', + }, + { + title: 'Balance', + Icon: , + path: '/user/balance', + }, + { + title: 'Send', + Icon: , + path: '/user/send', + }, +]; + +export const MenuDrawer = ({ open, onClose }: { open: boolean; onClose: () => void }) => { + const list = () => ( + {}}> + + {menuSchema.map(({ title, Icon, path }) => ( + + + + {Icon} + + + + + ))} + + + ); + + return ( +
+ + {list()} + +
+ ); +}; diff --git a/nym-browser-extension/src/components/ui/Modal/ErrorModal.tsx b/nym-browser-extension/src/components/ui/Modal/ErrorModal.tsx new file mode 100644 index 0000000000..713c83fdc9 --- /dev/null +++ b/nym-browser-extension/src/components/ui/Modal/ErrorModal.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { + Breakpoint, + Paper, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + SxProps, + Typography, +} from '@mui/material'; +import { Button } from '../Button'; + +export interface ErrorModalProps { + open: boolean; + children?: React.ReactNode; + title: React.ReactNode | string; + subtitle?: React.ReactNode | string; + sx?: SxProps; + fullWidth?: boolean; + maxWidth?: Breakpoint; + backdropProps?: object; + onClose?: () => void; +} + +export const ErrorModal = ({ + open, + onClose, + children, + title, + subtitle, + sx, + fullWidth, + maxWidth, + backdropProps, +}: ErrorModalProps) => { + const Title = ( + + + {title} + + {subtitle && + (typeof subtitle === 'string' ? ( + + {subtitle} + + ) : ( + subtitle + ))} + + ); + + return ( + + {Title} + {children} + + + + + ); +}; diff --git a/nym-browser-extension/src/components/ui/Modal/LoadingModal.tsx b/nym-browser-extension/src/components/ui/Modal/LoadingModal.tsx new file mode 100644 index 0000000000..82b0301c0f --- /dev/null +++ b/nym-browser-extension/src/components/ui/Modal/LoadingModal.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Box, CircularProgress, Modal, Stack, Typography, SxProps } from '@mui/material'; + +const modalStyle: SxProps = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 300, + bgcolor: 'background.paper', + boxShadow: 24, + borderRadius: '16px', + p: 4, +}; + +export const LoadingModal = ({ sx, backdropProps }: { sx?: SxProps; backdropProps?: object }) => ( + + `1px solid ${t.palette.grey[500]}`, ...modalStyle, ...sx }} textAlign="center"> + + + Please wait... + + + +); diff --git a/nym-browser-extension/src/components/ui/Modal/Modal.tsx b/nym-browser-extension/src/components/ui/Modal/Modal.tsx new file mode 100644 index 0000000000..18f88e28d0 --- /dev/null +++ b/nym-browser-extension/src/components/ui/Modal/Modal.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { + Breakpoint, + Paper, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + SxProps, + Typography, +} from '@mui/material'; + +export interface ConfirmationModalProps { + open: boolean; + children?: React.ReactNode; + title: React.ReactNode | string; + subtitle?: React.ReactNode | string; + ConfirmButton: React.ReactNode; + sx?: SxProps; + fullWidth?: boolean; + maxWidth?: Breakpoint; + backdropProps?: object; + onClose?: () => void; +} + +export const ConfirmationModal = ({ + open, + onClose, + children, + title, + subtitle, + ConfirmButton, + sx, + fullWidth, + maxWidth, + backdropProps, +}: ConfirmationModalProps) => { + const Title = ( + + + {title} + + {subtitle && + (typeof subtitle === 'string' ? ( + + {subtitle} + + ) : ( + subtitle + ))} + + ); + + return ( + + {Title} + {children} + {ConfirmButton} + + ); +}; diff --git a/nym-browser-extension/src/components/ui/Modal/index.tsx b/nym-browser-extension/src/components/ui/Modal/index.tsx new file mode 100644 index 0000000000..310ca4336c --- /dev/null +++ b/nym-browser-extension/src/components/ui/Modal/index.tsx @@ -0,0 +1,3 @@ +export * from './Modal'; +export * from './LoadingModal'; +export * from './ErrorModal'; diff --git a/nym-browser-extension/src/components/ui/Title/index.tsx b/nym-browser-extension/src/components/ui/Title/index.tsx new file mode 100644 index 0000000000..1bc6036d6e --- /dev/null +++ b/nym-browser-extension/src/components/ui/Title/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Typography } from '@mui/material'; + +const FONT_WEIGHT = 400; + +export const Title = ({ children }: { children: string }) => ( + + {children} + +); diff --git a/nym-browser-extension/src/components/ui/index.ts b/nym-browser-extension/src/components/ui/index.ts new file mode 100644 index 0000000000..5bee731a18 --- /dev/null +++ b/nym-browser-extension/src/components/ui/index.ts @@ -0,0 +1,8 @@ +export * from './AppBar'; +export * from './Button'; +export * from './BackButton'; +export * from './Logo'; +export * from './LogoWithText'; +export * from './MenuDrawer'; +export * from './Modal'; +export * from './Title'; diff --git a/nym-browser-extension/src/config.ts b/nym-browser-extension/src/config.ts new file mode 100644 index 0000000000..f66efff545 --- /dev/null +++ b/nym-browser-extension/src/config.ts @@ -0,0 +1,8 @@ +export const config = { + rpcUrl: process.env.RPC_URL || '', + validatorUrl: process.env.VALIDATOR_URL || '', + prefix: process.env.PREFIX || '', + mixnetContractAddress: process.env.MIXNET_CONTRACT_ADDRESS || '', + vestingContractAddress: process.env.VESTING_CONTRACT_ADDRESS || '', + denom: process.env.DENOM || '', +}; diff --git a/nym-browser-extension/src/context/app.tsx b/nym-browser-extension/src/context/app.tsx new file mode 100644 index 0000000000..9d052c1a60 --- /dev/null +++ b/nym-browser-extension/src/context/app.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import ValidatorClient from '@nymproject/nym-validator-client'; +import { ExtensionStorage } from '@nymproject/extension-storage'; +import { connectToValidator } from 'src/validator-client'; +import { unymToNym } from 'src/utils/coin'; +import { Currency, getTokenPrice } from 'src/utils/price'; + +type TAppContext = { + client?: ValidatorClient; + accounts: string[]; + balance?: string; + fiatBalance?: number; + denom: 'NYM'; + minorDenom: 'unym'; + currency: Currency; + showSeedForAccount?: string; + selectedAccount: string; + storage?: ExtensionStorage; + selectAccount: (accountName: string) => Promise; + setAccounts: (accounts: string[]) => void; + setShowSeedForAccount: (accountName?: string) => void; + handleUnlockWallet: (password: string) => void; + getBalance: () => void; +}; + +type TBalanceInNYMs = string; + +const DEFAULT_ACCOUNT_NAME = 'Default account'; + +const AppContext = React.createContext({} as TAppContext); + +export const AppProvider = ({ children }: { children: React.ReactNode }) => { + const [client, setClient] = useState(); + const [selectedAccount, setSelected] = useState(DEFAULT_ACCOUNT_NAME); + const [balance, setBalance] = useState(); + const [fiatBalance, setFiatBalance] = useState(); + const [accounts, setAccounts] = useState([]); + const [showSeedForAccount, setShowSeedForAccount] = useState(); + const [storage, setStorage] = useState(); + + const denom = 'NYM'; + const minorDenom = 'unym'; + const currency = 'gbp'; + + const handleUnlockWallet = async (password: string) => { + const store = await new ExtensionStorage(password); + const mnemonic = await store.read_mnemonic(DEFAULT_ACCOUNT_NAME); + const userAccounts = await store.get_all_mnemonic_keys(); + const clientFromMnemonic = await connectToValidator(mnemonic); + + setStorage(store); + setAccounts(userAccounts); + setClient(clientFromMnemonic); + }; + + const selectAccount = async (accountName: string) => { + const mnemonic = await storage!.read_mnemonic(accountName); + const clientFromMnemonic = await connectToValidator(mnemonic); + setSelected(accountName); + setClient(clientFromMnemonic); + }; + + const getFiatBalance = async (bal: number) => { + const tokenPrice = await getTokenPrice('nym', currency); + const fiatBal = tokenPrice.nym.gbp * bal; + return fiatBal; + }; + + const getBalance = async () => { + const bal = await client?.getBalance(client.address); + if (bal) { + const nym = unymToNym(Number(bal.amount)); + const fiat = await getFiatBalance(nym); + setFiatBalance(fiat); + setBalance(nym.toString()); + } + }; + + useEffect(() => { + if (client) { + getBalance(); + } + }, [client]); + + const value = useMemo( + () => ({ + client, + accounts, + balance, + fiatBalance, + currency, + denom, + minorDenom, + selectedAccount, + storage, + handleUnlockWallet, + getBalance, + setShowSeedForAccount, + showSeedForAccount, + setAccounts, + selectAccount, + }), + [client, accounts, balance, fiatBalance, denom, minorDenom, selectedAccount, showSeedForAccount, storage], + ); + + return {children}; +}; + +export const useAppContext = () => React.useContext(AppContext); diff --git a/nym-browser-extension/src/context/index.tsx b/nym-browser-extension/src/context/index.tsx new file mode 100644 index 0000000000..5bcd157c3c --- /dev/null +++ b/nym-browser-extension/src/context/index.tsx @@ -0,0 +1,3 @@ +export * from './app'; +export * from './send'; +export * from './register'; diff --git a/nym-browser-extension/src/context/register.tsx b/nym-browser-extension/src/context/register.tsx new file mode 100644 index 0000000000..2204b07d0b --- /dev/null +++ b/nym-browser-extension/src/context/register.tsx @@ -0,0 +1,71 @@ +import React, { useMemo, useState } from 'react'; +import { ExtensionStorage } from '@nymproject/extension-storage'; + +const RegisterContext = React.createContext({} as TRegisterContext); + +type TRegisterContext = { + userPassword: string; + userMnemonic: string; + accountName: string; + checkAccountName: () => Promise; + setUserPassword: (password: string) => void; + setUserMnemonic: (mnemonic: string) => void; + setAccountName: (name: string) => void; + createAccount: (args: { mnemonic: string; password: string; accName: string }) => Promise; + importAccount: () => Promise; + resetState: () => void; +}; + +export const RegisterContextProvider = ({ children }: { children: React.ReactNode }) => { + const [userPassword, setUserPassword] = useState(''); + const [userMnemonic, setUserMnemonic] = useState(''); + const [accountName, setAccountName] = useState(''); + + const resetState = () => { + setUserMnemonic(''); + setUserPassword(''); + setAccountName(''); + }; + + const createAccount = async ({ + mnemonic, + password, + accName, + }: { + mnemonic: string; + password: string; + accName: string; + }) => { + const storage = await new ExtensionStorage(password); + await storage.store_mnemonic(accName, mnemonic); + }; + + const importAccount = async () => { + const storage = await new ExtensionStorage(userPassword); + await storage.store_mnemonic(accountName, userMnemonic); + const accounts = await storage.get_all_mnemonic_keys(); + return accounts; + }; + + const checkAccountName = async () => true; + + const value = useMemo( + () => ({ + userPassword, + setUserPassword, + userMnemonic, + accountName, + setAccountName, + setUserMnemonic, + createAccount, + checkAccountName, + importAccount, + resetState, + }), + [userPassword, userMnemonic, accountName], + ); + + return {children}; +}; + +export const useRegisterContext = () => React.useContext(RegisterContext); diff --git a/nym-browser-extension/src/context/send.tsx b/nym-browser-extension/src/context/send.tsx new file mode 100644 index 0000000000..a5c4295ff8 --- /dev/null +++ b/nym-browser-extension/src/context/send.tsx @@ -0,0 +1,112 @@ +import React, { useMemo, useState } from 'react'; +import { DecCoin } from '@nymproject/types'; +import { useNavigate } from 'react-router-dom'; +import { nymToUnym } from 'src/utils/coin'; +import { TTransaction } from 'src/types'; +import { Fee, useGetFee } from 'src/hooks/useGetFee'; +import { createFeeObject } from 'src/utils/fee'; +import { useAppContext } from './app'; + +type TSendContext = { + address?: string; + amount?: DecCoin; + transaction?: TTransaction; + fee?: Fee; + handleChangeAddress: (address?: string) => void; + handleChangeAmount: (amount?: DecCoin) => void; + handleSend: () => void; + resetTx: () => void; + onDone: () => void; + handleGetFee: (address: string, amount: string) => Promise; +}; + +const SendContext = React.createContext({} as TSendContext); + +export const SendProvider = ({ children }: { children: React.ReactNode }) => { + const [address, setAddress] = useState(); + const [amount, setAmount] = useState(); + const [transaction, setTransaction] = useState(); + + const { client, minorDenom } = useAppContext(); + const navigate = useNavigate(); + + const handleChangeAddress = (_address?: string) => setAddress(_address); + + const handleChangeAmount = (_amount?: DecCoin) => setAmount(_amount); + + const { getFee, fee } = useGetFee(); + + const handleGetFee = async (addressVal: string, amountVal: string) => { + const unym = nymToUnym(Number(amountVal)); + + if (client) { + // client loses its 'this' context when passing the method + // TODO find a better way of doing this. + getFee(client.simulateSend.bind(client), { + signingAddress: client.address, + from: client.address, + to: addressVal, + amount: [{ amount: unym.toString(), denom: minorDenom }], + }); + } + }; + + const handleSend = async () => { + setTransaction({ status: 'loading', type: 'send' }); + let unyms; + + if (!Number(amount?.amount)) { + setTransaction({ status: 'error', type: 'send', message: 'Amount is not a valid number' }); + } + + if (amount) { + unyms = nymToUnym(Number(amount.amount)); + } + + if (client && address && unyms) { + try { + const response = await client.send( + address, + [{ amount: unyms.toString(), denom: minorDenom }], + createFeeObject(fee?.unym), + ); + + setTransaction({ status: 'success', type: 'send', txHash: response?.transactionHash }); + } catch (e) { + setTransaction({ + status: 'error', + type: 'send', + message: e instanceof Error ? e.message : 'Error making send transaction. Please try again', + }); + } + } + }; + + const resetTx = () => { + setTransaction(undefined); + }; + + const onDone = () => { + navigate('/user/balance'); + }; + + const value = useMemo( + () => ({ + address, + amount, + transaction, + fee, + handleChangeAddress, + handleChangeAmount, + handleSend, + resetTx, + onDone, + handleGetFee, + }), + [address, amount, transaction, fee], + ); + + return {children}; +}; + +export const useSendContext = () => React.useContext(SendContext); diff --git a/nym-browser-extension/src/hooks/useCreatePassword.tsx b/nym-browser-extension/src/hooks/useCreatePassword.tsx new file mode 100644 index 0000000000..33aa94ca59 --- /dev/null +++ b/nym-browser-extension/src/hooks/useCreatePassword.tsx @@ -0,0 +1,21 @@ +import { useState } from 'react'; + +export const useCreatePassword = () => { + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isSafePassword, setIsSafePassword] = useState(false); + const [hasReadTerms, setHasReadTerms] = useState(false); + + const canProceed = isSafePassword && hasReadTerms && password === confirmPassword; + + return { + password, + setPassword, + confirmPassword, + setConfirmPassword, + setIsSafePassword, + canProceed, + setHasReadTerms, + hasReadTerms, + }; +}; diff --git a/nym-browser-extension/src/hooks/useGetFee.ts b/nym-browser-extension/src/hooks/useGetFee.ts new file mode 100644 index 0000000000..af6080a2f4 --- /dev/null +++ b/nym-browser-extension/src/hooks/useGetFee.ts @@ -0,0 +1,40 @@ +import Big from 'big.js'; +import { useState } from 'react'; +import { unymToNym } from 'src/utils/coin'; + +export type Fee = { nym: number; unym: number }; + +export function useGetFee() { + const [fee, setFee] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(); + + async function getFee(txReq: (args: T) => Promise, args: T) { + setError(undefined); + setIsLoading(true); + + try { + const txFee = await txReq(args); + + if (txFee) { + const feeWithMultiplyer = Big(txFee).mul(1); + console.log(fee); + + const txFeeInNyms = unymToNym(feeWithMultiplyer); + + setFee({ nym: Number(txFeeInNyms), unym: Number(feeWithMultiplyer) }); + } + + if (!txFee) { + setError('Unable to calculate fee'); + } + } catch (e) { + console.error(e); + setError(`Unable to get estimated fee: ${e}`); + } finally { + setIsLoading(false); + } + } + + return { fee, getFee, isLoading, error }; +} diff --git a/nym-browser-extension/src/index.html b/nym-browser-extension/src/index.html new file mode 100644 index 0000000000..5c2e4f5473 --- /dev/null +++ b/nym-browser-extension/src/index.html @@ -0,0 +1,12 @@ + + + + + + + Nym browser extension + + +
+ + diff --git a/nym-browser-extension/src/index.tsx b/nym-browser-extension/src/index.tsx new file mode 100644 index 0000000000..56859d9574 --- /dev/null +++ b/nym-browser-extension/src/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; + +const rootDomElem = document.getElementById('root'); + +if (rootDomElem) { + const root = createRoot(rootDomElem); + root.render(); +} diff --git a/nym-browser-extension/src/layouts/AppLayout.tsx b/nym-browser-extension/src/layouts/AppLayout.tsx new file mode 100644 index 0000000000..be5a54398e --- /dev/null +++ b/nym-browser-extension/src/layouts/AppLayout.tsx @@ -0,0 +1,8 @@ +import { Container } from '@mui/material'; +import React from 'react'; + +export const AppLayout = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); diff --git a/nym-browser-extension/src/layouts/CenteredLogo.tsx b/nym-browser-extension/src/layouts/CenteredLogo.tsx new file mode 100644 index 0000000000..5a59bd3673 --- /dev/null +++ b/nym-browser-extension/src/layouts/CenteredLogo.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { LogoWithText } from 'src/components/ui'; + +const layoutStyle = { + height: '100%', + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: 'repeat(3, 1fr)', + gridColumnGap: '0px', + gridRowGap: '0px', + p: 2, +}; + +export const CenteredLogoLayout = ({ + title, + description, + Actions, +}: { + title: string; + description?: string; + Actions: React.ReactNode; +}) => ( + + + + {Actions} + +); diff --git a/nym-browser-extension/src/layouts/PageLayout.tsx b/nym-browser-extension/src/layouts/PageLayout.tsx new file mode 100644 index 0000000000..07bd62797d --- /dev/null +++ b/nym-browser-extension/src/layouts/PageLayout.tsx @@ -0,0 +1,38 @@ +import React, { useCallback, useState } from 'react'; +import { Box, IconButton } from '@mui/material'; +import MenuIcon from '@mui/icons-material/Menu'; +import { AppBar, BackButton, MenuDrawer } from 'src/components/ui'; +import { useLocation } from 'react-router-dom'; + +const layoutStyle = { + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: '50px 1fr', + gridColumnGap: '0px', + gridRowGap: '0px', +}; + +export const PageLayout = ({ children, onBack }: { children: React.ReactNode; onBack?: () => void }) => { + const [menuOpen, setMenuOpen] = useState(false); + + const location = useLocation(); + + const MenuAction = useCallback( + () => ( + setMenuOpen(true)}> + + + ), + [], + ); + + const Action = location.pathname.includes('balance') ? MenuAction : BackButton; + + return ( + + } /> + setMenuOpen(false)} /> + {children} + + ); +}; diff --git a/nym-browser-extension/src/layouts/TopLogo.tsx b/nym-browser-extension/src/layouts/TopLogo.tsx new file mode 100644 index 0000000000..b8b9a75ccf --- /dev/null +++ b/nym-browser-extension/src/layouts/TopLogo.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { BackButton, LogoWithText } from 'src/components/ui'; + +const layoutStyle = { + height: '100%', + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplaterows: '1fr 2fr 1fr', + gridColumnGap: '0px', + gridRowGap: '0px', + position: 'relative', + p: 2, +}; + +export const TopLogoLayout = ({ + title, + description, + children, + Actions, +}: { + title: string; + description?: string; + children: React.ReactNode; + Actions: React.ReactNode; +}) => ( + + + + + + + + {children} + {Actions} + +); diff --git a/nym-browser-extension/src/layouts/index.ts b/nym-browser-extension/src/layouts/index.ts new file mode 100644 index 0000000000..1126750950 --- /dev/null +++ b/nym-browser-extension/src/layouts/index.ts @@ -0,0 +1,3 @@ +export * from './AppLayout'; +export * from './CenteredLogo'; +export * from './TopLogo'; diff --git a/nym-browser-extension/src/manifest.json b/nym-browser-extension/src/manifest.json new file mode 100644 index 0000000000..47ced7fc78 --- /dev/null +++ b/nym-browser-extension/src/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "Nym browser extension", + "description": "Nym browser extension - Wallet & credentials", + "version": "0.1.0", + "manifest_version": 3, + "action": { + "default_popup": "index.html", + "default_title": "Nym - Browser extension" + }, + "icons": { + "16": "favicon-16x16.png", + "32": "favicon-32x32.png", + "48": "favicon-48x48.png" + }, + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" + } +} diff --git a/nym-browser-extension/src/pages/accounts/Accounts.tsx b/nym-browser-extension/src/pages/accounts/Accounts.tsx new file mode 100644 index 0000000000..00b8f065f2 --- /dev/null +++ b/nym-browser-extension/src/pages/accounts/Accounts.tsx @@ -0,0 +1,43 @@ +import React, { useEffect } from 'react'; +import { PageLayout } from 'src/layouts/PageLayout'; +import { Stack } from '@mui/material'; +import { Add, ArrowDownward } from '@mui/icons-material'; +import { AccountList, Button } from 'src/components'; +import { ViewSeedPhrase } from 'src/components/accounts/ViewSeedPhrase'; +import { useAppContext, useRegisterContext } from 'src/context'; +import { useLocation, useNavigate } from 'react-router-dom'; + +export const Accounts = () => { + const { showSeedForAccount, setShowSeedForAccount } = useAppContext(); + const { resetState } = useRegisterContext(); + + useEffect(() => { + resetState(); + }, []); + + const location = useLocation(); + const navigate = useNavigate(); + + const handleAddAccount = () => navigate(`${location.pathname}/add-account`); + + const handleImportAccount = () => navigate(`${location.pathname}/import-account`); + + const onBack = () => navigate('/user/balance'); + + return ( + + {showSeedForAccount && ( + setShowSeedForAccount(undefined)} /> + )} + + + + + + + ); +}; diff --git a/nym-browser-extension/src/pages/accounts/AddAccount.tsx b/nym-browser-extension/src/pages/accounts/AddAccount.tsx new file mode 100644 index 0000000000..50849ea9d3 --- /dev/null +++ b/nym-browser-extension/src/pages/accounts/AddAccount.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useRegisterContext } from 'src/context'; +import { SeedPhraseTemplate } from 'src/pages/templates'; + +export const AddAccount = () => { + const { setUserMnemonic } = useRegisterContext(); + const navigate = useNavigate(); + + const onNext = (seedPhrase: string) => { + setUserMnemonic(seedPhrase); + navigate('/user/accounts/name-account'); + }; + + return ; +}; diff --git a/nym-browser-extension/src/pages/accounts/Complete.tsx b/nym-browser-extension/src/pages/accounts/Complete.tsx new file mode 100644 index 0000000000..b94616c531 --- /dev/null +++ b/nym-browser-extension/src/pages/accounts/Complete.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { SetupCompleteTemplate } from 'src/pages/templates/Complete'; + +export const SetupComplete = () => { + const navigate = useNavigate(); + const handleOnDone = () => { + navigate('/user/accounts'); + }; + + return ( + + ); +}; diff --git a/nym-browser-extension/src/pages/accounts/ConfirmPassword.tsx b/nym-browser-extension/src/pages/accounts/ConfirmPassword.tsx new file mode 100644 index 0000000000..10f3efd549 --- /dev/null +++ b/nym-browser-extension/src/pages/accounts/ConfirmPassword.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; +import { Button } from 'src/components'; +import { useAppContext, useRegisterContext } from 'src/context'; +import { TopLogoLayout } from 'src/layouts'; +import { useNavigate } from 'react-router-dom'; + +export const ConfirmPassword = () => { + const { setAccounts } = useAppContext(); + const { userPassword, setUserPassword, importAccount } = useRegisterContext(); + const [error, setError] = useState(); + + const navigate = useNavigate(); + + const handleOnComplete = async () => { + try { + const accounts = await importAccount(); + setAccounts(accounts); + navigate('/user/accounts/complete'); + } catch (e) { + setError('Incorrect password. Please try again'); + } + }; + + const onChange = (password: string) => { + setError(undefined); + setUserPassword(password); + }; + + return ( + + Confirm + + } + > + + + ); +}; diff --git a/nym-browser-extension/src/pages/accounts/ImportAccount.tsx b/nym-browser-extension/src/pages/accounts/ImportAccount.tsx new file mode 100644 index 0000000000..b630eef986 --- /dev/null +++ b/nym-browser-extension/src/pages/accounts/ImportAccount.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useRegisterContext } from 'src/context/register'; +import { ImportAccountTemplate } from '../templates'; + +export const ImportAccount = () => { + const { userMnemonic, setUserMnemonic } = useRegisterContext(); + const navigate = useNavigate(); + + const handleOnNext = () => { + navigate('/user/accounts/name-account'); + }; + + return ( + + ); +}; diff --git a/nym-browser-extension/src/pages/accounts/NameAccount.tsx b/nym-browser-extension/src/pages/accounts/NameAccount.tsx new file mode 100644 index 0000000000..7cb2bc9b66 --- /dev/null +++ b/nym-browser-extension/src/pages/accounts/NameAccount.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { TextField } from '@mui/material'; +import { Button } from 'src/components'; +import { useRegisterContext } from 'src/context/register'; +import { TopLogoLayout } from 'src/layouts'; +import { useNavigate } from 'react-router-dom'; +import { useAppContext } from 'src/context'; + +export const NameAccount = () => { + const { accountName, setAccountName } = useRegisterContext(); + const { storage } = useAppContext(); + const navigate = useNavigate(); + + const [error, setError] = useState(); + + const handleNext = async () => { + const accountNameExists = await storage?.has_mnemonic(accountName); + if (accountNameExists) { + setError('Account name already exists. Please choose another account name'); + } else { + navigate('/user/accounts/confirm-password'); + } + }; + + return ( + + Next + + } + > + { + setError(undefined); + setAccountName(e.target.value); + }} + error={!!error} + helperText={error} + /> + + ); +}; diff --git a/nym-browser-extension/src/pages/accounts/index.ts b/nym-browser-extension/src/pages/accounts/index.ts new file mode 100644 index 0000000000..ed37aac8ad --- /dev/null +++ b/nym-browser-extension/src/pages/accounts/index.ts @@ -0,0 +1,6 @@ +export * from './Accounts'; +export * from './AddAccount'; +export * from './Complete'; +export * from './ConfirmPassword'; +export * from './ImportAccount'; +export * from './NameAccount'; diff --git a/nym-browser-extension/src/pages/auth/ForgotPassword.tsx b/nym-browser-extension/src/pages/auth/ForgotPassword.tsx new file mode 100644 index 0000000000..5910a1d77f --- /dev/null +++ b/nym-browser-extension/src/pages/auth/ForgotPassword.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Typography } from '@mui/material'; +import { Box } from '@mui/system'; +import { TopLogoLayout } from 'src/layouts'; + +const steps = [ + 'Make sure you have your mnemonic saved', + 'Uninstal Nym extension wallet', + 'Reinstal Nym extension wallet', + 'Import your account using seed phrase', + 'Create new password', +]; + +export const ForgotPassword = () => ( + }> + + {steps.map((step, index) => ( + + {`${index + 1}. ${step}`} + + ))} + + +); diff --git a/nym-browser-extension/src/pages/auth/Login.tsx b/nym-browser-extension/src/pages/auth/Login.tsx new file mode 100644 index 0000000000..11db476a55 --- /dev/null +++ b/nym-browser-extension/src/pages/auth/Login.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Stack, TextField } from '@mui/material'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { Button } from 'src/components/ui'; +import { CenteredLogoLayout } from 'src/layouts/CenteredLogo'; +import { useAppContext } from 'src/context'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { validationSchema } from './validationSchema'; + +export const Login = () => { + const { handleUnlockWallet } = useAppContext(); + const navigate = useNavigate(); + const location = useLocation(); + + const { + register, + handleSubmit, + setError, + formState: { errors, isSubmitting }, + } = useForm({ resolver: zodResolver(validationSchema), defaultValues: { password: '' } }); + + const onSubmit = async (data: { password: string }) => { + try { + await handleUnlockWallet(data.password); + } catch (e) { + setError('password', { message: 'Incorrect password. Please try again.' }); + } + }; + + return ( + + + + + + + } + /> + ); +}; diff --git a/nym-browser-extension/src/pages/auth/index.tsx b/nym-browser-extension/src/pages/auth/index.tsx new file mode 100644 index 0000000000..80aa38c3c1 --- /dev/null +++ b/nym-browser-extension/src/pages/auth/index.tsx @@ -0,0 +1,2 @@ +export * from './Login'; +export * from './ForgotPassword'; diff --git a/nym-browser-extension/src/pages/auth/validationSchema.ts b/nym-browser-extension/src/pages/auth/validationSchema.ts new file mode 100644 index 0000000000..a24b838a9c --- /dev/null +++ b/nym-browser-extension/src/pages/auth/validationSchema.ts @@ -0,0 +1,5 @@ +import * as z from 'zod'; + +export const validationSchema = z.object({ + password: z.string().min(1, { message: 'Required' }), +}); diff --git a/nym-browser-extension/src/pages/balance/index.tsx b/nym-browser-extension/src/pages/balance/index.tsx new file mode 100644 index 0000000000..a4fc0cd4e6 --- /dev/null +++ b/nym-browser-extension/src/pages/balance/index.tsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Box, Stack, IconButton, Typography } from '@mui/material'; +import { ArrowDownwardRounded, ArrowUpwardRounded, TollRounded } from '@mui/icons-material'; +import { PageLayout } from 'src/layouts/PageLayout'; +import { Address, Balance, ReceiveModal } from 'src/components'; + +type ActionsSchema = Array<{ + title: string; + Icon: React.ReactNode; + onClick: () => void; +}>; + +const Actions = ({ actionsSchema }: { actionsSchema: ActionsSchema }) => ( + + {actionsSchema.map(({ title, Icon, onClick }) => ( + + + {Icon} + + {title} + + ))} + +); + +export const BalancePage = () => { + const [showReceiveModal, setShowReceiveModal] = useState(false); + const navigate = useNavigate(); + + const actionsSchema = [ + { + title: 'Send', + Icon: , + onClick: () => navigate('/user/send'), + }, + { + title: 'Receive', + Icon: , + onClick: () => setShowReceiveModal(true), + }, + { + title: 'Buy', + Icon: , + onClick: () => navigate('/user/balance'), + }, + ]; + + return ( + + + setShowReceiveModal(false)} /> +
+ + + + + ); +}; diff --git a/nym-browser-extension/src/pages/delegation/index.tsx b/nym-browser-extension/src/pages/delegation/index.tsx new file mode 100644 index 0000000000..4e89134021 --- /dev/null +++ b/nym-browser-extension/src/pages/delegation/index.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const Delegation = () =>

Delegation

; diff --git a/nym-browser-extension/src/pages/home/index.tsx b/nym-browser-extension/src/pages/home/index.tsx new file mode 100644 index 0000000000..21cc23d920 --- /dev/null +++ b/nym-browser-extension/src/pages/home/index.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Stack } from '@mui/system'; +import { Button } from 'src/components/ui'; +import { CenteredLogoLayout } from 'src/layouts'; +import { Link } from 'react-router-dom'; + +export const Home = () => ( + + + + + + + + + } + /> +); diff --git a/nym-browser-extension/src/pages/index.ts b/nym-browser-extension/src/pages/index.ts new file mode 100644 index 0000000000..47c840f752 --- /dev/null +++ b/nym-browser-extension/src/pages/index.ts @@ -0,0 +1,8 @@ +export * from './accounts'; +export * from './auth'; +export * from './balance'; +export * from './home'; +export * from './receive'; +export * from './send'; +export * from './settings'; +export * from './delegation'; diff --git a/nym-browser-extension/src/pages/receive/index.tsx b/nym-browser-extension/src/pages/receive/index.tsx new file mode 100644 index 0000000000..cdccb23d3e --- /dev/null +++ b/nym-browser-extension/src/pages/receive/index.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const Receive = () =>

Receive

; diff --git a/nym-browser-extension/src/pages/register/Complete.tsx b/nym-browser-extension/src/pages/register/Complete.tsx new file mode 100644 index 0000000000..601e355910 --- /dev/null +++ b/nym-browser-extension/src/pages/register/Complete.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { SetupCompleteTemplate } from '../templates/Complete'; + +export const SetupComplete = ({ onDone }: { onDone: () => void }) => ( + +); diff --git a/nym-browser-extension/src/pages/register/CreatePasswordOnExistingAccount.tsx b/nym-browser-extension/src/pages/register/CreatePasswordOnExistingAccount.tsx new file mode 100644 index 0000000000..77a19b7e5e --- /dev/null +++ b/nym-browser-extension/src/pages/register/CreatePasswordOnExistingAccount.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { useCreatePassword } from 'src/hooks/useCreatePassword'; +import { useRegisterContext } from 'src/context/register'; +import { CreatePasswordTemplate } from 'src/pages/templates/CreatePassword'; + +export const CreatePasswordOnExistingAccount = ({ onComplete }: { onComplete: () => void }) => { + const passwordState = useCreatePassword(); + const { createAccount, userMnemonic } = useRegisterContext(); + + const handleOnComplete = async () => { + await createAccount({ mnemonic: userMnemonic, password: passwordState.password, accName: 'Default account' }); + onComplete(); + }; + + return ; +}; diff --git a/nym-browser-extension/src/pages/register/CreatePasswordOnNewAccount.tsx b/nym-browser-extension/src/pages/register/CreatePasswordOnNewAccount.tsx new file mode 100644 index 0000000000..2a5190ac7c --- /dev/null +++ b/nym-browser-extension/src/pages/register/CreatePasswordOnNewAccount.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { useCreatePassword } from 'src/hooks/useCreatePassword'; +import { useRegisterContext } from 'src/context/register'; +import { CreatePasswordTemplate } from 'src/pages/templates/CreatePassword'; + +export const CreatePasswordOnNewAccount = ({ onNext }: { onNext: () => void }) => { + const passwordState = useCreatePassword(); + const { setUserPassword } = useRegisterContext(); + + const handleCreateAccount = async () => { + await setUserPassword(passwordState.password); + onNext(); + }; + + return ; +}; diff --git a/nym-browser-extension/src/pages/register/ImportAccount.tsx b/nym-browser-extension/src/pages/register/ImportAccount.tsx new file mode 100644 index 0000000000..76cf420471 --- /dev/null +++ b/nym-browser-extension/src/pages/register/ImportAccount.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useRegisterContext } from 'src/context/register'; +import { ImportAccountTemplate } from '../templates/ImportAccount'; + +export const ImportAccount = () => { + const navigate = useNavigate(); + const location = useLocation(); + + const { setUserMnemonic, userMnemonic } = useRegisterContext(); + + const handleNext = async () => { + navigate(`${location.pathname}/create-password`); + }; + + return ( + + ); +}; diff --git a/nym-browser-extension/src/pages/register/SeedPhrase.tsx b/nym-browser-extension/src/pages/register/SeedPhrase.tsx new file mode 100644 index 0000000000..7218aa8da7 --- /dev/null +++ b/nym-browser-extension/src/pages/register/SeedPhrase.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useRegisterContext } from 'src/context/register'; +import { SeedPhraseTemplate } from '../templates/SeedPhrase'; + +export const SeedPhrase = () => { + const navigate = useNavigate(); + + const { createAccount, userPassword } = useRegisterContext(); + + const handleEncryptSeedPhrase = async (seedPhrase: string) => { + await createAccount({ mnemonic: seedPhrase, password: userPassword, accName: 'Default account' }); + navigate('/register/complete'); + }; + + return ; +}; diff --git a/nym-browser-extension/src/pages/register/index.ts b/nym-browser-extension/src/pages/register/index.ts new file mode 100644 index 0000000000..691cc9ea35 --- /dev/null +++ b/nym-browser-extension/src/pages/register/index.ts @@ -0,0 +1,5 @@ +export * from './Complete'; +export * from './CreatePasswordOnExistingAccount'; +export * from './CreatePasswordOnNewAccount'; +export * from './ImportAccount'; +export * from './SeedPhrase'; diff --git a/nym-browser-extension/src/pages/send/Confirmation.tsx b/nym-browser-extension/src/pages/send/Confirmation.tsx new file mode 100644 index 0000000000..2e6d305f4c --- /dev/null +++ b/nym-browser-extension/src/pages/send/Confirmation.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { Box, Divider, ListItem, ListItemText, Stack, Typography } from '@mui/material'; +import { Button } from 'src/components'; +import { PageLayout } from 'src/layouts/PageLayout'; +import { useAppContext, useSendContext } from 'src/context'; +import { ErrorModal, LoadingModal } from 'src/components/ui/Modal'; +import { SendConfirmationModal } from 'src/components/send/SendConfirmationModal'; +import { blockExplorerUrl } from 'src/urls'; + +const InfoItem = ({ label, value }: { label: string; value: string }) => ( + + + + {label} + + } + secondary={ + + {value} + + } + /> + + + +); + +export const SendConfirmationPage = ({ onCancel }: { onCancel: () => void }) => { + const { client, denom } = useAppContext(); + const { address, amount, fee, handleSend, transaction, resetTx, onDone } = useSendContext(); + + const calculateTotal = () => (Number(fee?.nym) + Number(amount?.amount)).toString(); + + return ( + + {transaction?.status === 'success' && ( + + )} + {transaction?.status === 'loading' && } + {transaction?.status === 'error' && ( + + {transaction.message} + + )} + + + + + + + + + + + + + ); +}; diff --git a/nym-browser-extension/src/pages/send/index.tsx b/nym-browser-extension/src/pages/send/index.tsx new file mode 100644 index 0000000000..c6aa42bbe4 --- /dev/null +++ b/nym-browser-extension/src/pages/send/index.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { Box, Divider, Stack, Typography } from '@mui/material'; +import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField'; +import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; +import { DecCoin } from '@nymproject/types'; +import { Address, Button } from 'src/components'; +import { PageLayout } from 'src/layouts/PageLayout'; +import { SendProvider, useAppContext, useSendContext } from 'src/context'; +import { SendConfirmationPage } from './Confirmation'; + +const SendPage = ({ onConfirm }: { onConfirm: () => void }) => { + const [isValidAddress, setIsValidAddress] = useState(false); + const [isValidAmount, setIsValidAmount] = useState(false); + + const { address, amount, handleChangeAddress, handleChangeAmount, handleGetFee } = useSendContext(); + const { balance } = useAppContext(); + + const handleNext = async () => { + if (address && amount) { + await handleGetFee(address, amount.amount); + onConfirm(); + } + }; + + return ( + + +
+ handleChangeAddress(_address)} + onValidate={setIsValidAddress} + initialValue={address} + /> + handleChangeAmount(_amount)} + onValidate={(_: any, isValid: boolean) => setIsValidAmount(isValid)} + /> + + + Account balance + {balance} NYM + + + + Est. fee for this transaction will be calculated on the next page + + + + + + ); +}; + +export const Send = () => { + const [showConfirmation, setShowConfirmation] = useState(false); + + return ( + + {showConfirmation ? ( + setShowConfirmation(false)} /> + ) : ( + setShowConfirmation(true)} /> + )} + + ); +}; diff --git a/nym-browser-extension/src/pages/settings/index.tsx b/nym-browser-extension/src/pages/settings/index.tsx new file mode 100644 index 0000000000..0beaaf3dc1 --- /dev/null +++ b/nym-browser-extension/src/pages/settings/index.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const Settings = () =>

Settings

; diff --git a/nym-browser-extension/src/pages/templates/Complete.tsx b/nym-browser-extension/src/pages/templates/Complete.tsx new file mode 100644 index 0000000000..a774516d4c --- /dev/null +++ b/nym-browser-extension/src/pages/templates/Complete.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { Button } from 'src/components/ui'; +import { CenteredLogoLayout } from 'src/layouts'; + +export const SetupCompleteTemplate = ({ + title, + description, + onDone, +}: { + title: string; + description: string; + onDone: () => void; +}) => ( + + + + } + /> +); diff --git a/nym-browser-extension/src/pages/templates/CreatePassword.tsx b/nym-browser-extension/src/pages/templates/CreatePassword.tsx new file mode 100644 index 0000000000..cb866d545b --- /dev/null +++ b/nym-browser-extension/src/pages/templates/CreatePassword.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { FormControlLabel, Checkbox, Stack, Typography, Box } from '@mui/material'; +import { TopLogoLayout } from 'src/layouts/TopLogo'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; +import { PasswordStrength } from '@nymproject/react/password-strength/PasswordStrength'; +import { Button } from 'src/components/ui'; + +type TCreatePassword = { + canProceed: boolean; + password: string; + confirmPassword: string; + hasReadTerms: boolean; + setHasReadTerms: (hasReadTerms: boolean) => void; + setIsSafePassword: (isSafe: boolean) => void; + setConfirmPassword: (password: string) => void; + onNext: () => void; + setPassword: (password: string) => void; +}; + +export const CreatePasswordTemplate = ({ + canProceed, + onNext, + password, + setPassword, + confirmPassword, + setIsSafePassword, + setConfirmPassword, + setHasReadTerms, + hasReadTerms, +}: TCreatePassword) => ( + + Next + + } + > + + setPassword(_password)} + label="Password" + /> + + setIsSafePassword(isSafe)} /> + + + + setConfirmPassword(_password)} + label="Confirm password" + /> + + + I have read and agree with the Terms of use} + control={ setHasReadTerms(checked)} />} + /> + +); diff --git a/nym-browser-extension/src/pages/templates/ImportAccount.tsx b/nym-browser-extension/src/pages/templates/ImportAccount.tsx new file mode 100644 index 0000000000..03a560f7e3 --- /dev/null +++ b/nym-browser-extension/src/pages/templates/ImportAccount.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { TextField } from '@mui/material'; +import { Button } from 'src/components'; +import { TopLogoLayout } from 'src/layouts'; + +export const ImportAccountTemplate = ({ + userMnemonic, + onChangeUserMnemonic, + onNext, +}: { + userMnemonic: string; + onChangeUserMnemonic: (mnemonic: string) => void; + onNext: () => void; +}) => ( + + Next + + } + > + onChangeUserMnemonic(e.target.value)} + multiline + autoFocus={false} + fullWidth + inputProps={{ + style: { + height: '160px', + }, + }} + InputLabelProps={{ shrink: true }} + sx={{ + 'input::-webkit-textfield-decoration-container': { + alignItems: 'start', + }, + }} + /> + +); diff --git a/nym-browser-extension/src/pages/templates/SeedPhrase.tsx b/nym-browser-extension/src/pages/templates/SeedPhrase.tsx new file mode 100644 index 0000000000..f8c47150a8 --- /dev/null +++ b/nym-browser-extension/src/pages/templates/SeedPhrase.tsx @@ -0,0 +1,60 @@ +import React, { useRef, useState } from 'react'; +import { Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material'; +import { TopLogoLayout } from 'src/layouts/TopLogo'; +import { Button } from 'src/components/ui'; +import { generateMnemonmic } from 'src/validator-client'; + +export const SeedPhraseTemplate = ({ onNext }: { onNext: (seedPhrase: string) => void }) => { + const [isConfirmed, setIsconfirmed] = useState(false); + + const seedPhrase = useRef(generateMnemonmic()); + + return ( + onNext(seedPhrase.current)} + > + Next + + } + > + + + Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future + + + + + setIsconfirmed(checked)} />} + /> + + + ); +}; diff --git a/nym-browser-extension/src/pages/templates/index.ts b/nym-browser-extension/src/pages/templates/index.ts new file mode 100644 index 0000000000..b7c8c43756 --- /dev/null +++ b/nym-browser-extension/src/pages/templates/index.ts @@ -0,0 +1,4 @@ +export * from './Complete'; +export * from './CreatePassword'; +export * from './ImportAccount'; +export * from './SeedPhrase'; diff --git a/nym-browser-extension/src/routes/index.tsx b/nym-browser-extension/src/routes/index.tsx new file mode 100644 index 0000000000..a6cd93be4f --- /dev/null +++ b/nym-browser-extension/src/routes/index.tsx @@ -0,0 +1,35 @@ +import React, { useEffect, useState } from 'react'; +import { BrowserRouter, MemoryRouter, Route, Routes } from 'react-router-dom'; +import { Home } from 'src/pages'; +import { ExtensionStorage } from '@nymproject/extension-storage'; +import { RegisterRoutes } from './register'; +import { UserRoutes } from './user'; +import { LoginRoutes } from './login'; + +const Router = process.env.NODE_ENV === 'development' ? BrowserRouter : MemoryRouter; + +export const AppRoutes = () => { + const [userHasAccount, setUserHasAccount] = useState(null); + + useEffect(() => { + const checkUserHasAccount = async () => { + const hasAccount = await ExtensionStorage.exists(); + setUserHasAccount(hasAccount); + }; + + checkUserHasAccount(); + }, []); + + if (userHasAccount === null) return null; + + return ( + + + : } /> + } /> + } /> + } /> + + + ); +}; diff --git a/nym-browser-extension/src/routes/login/index.tsx b/nym-browser-extension/src/routes/login/index.tsx new file mode 100644 index 0000000000..c61c45c095 --- /dev/null +++ b/nym-browser-extension/src/routes/login/index.tsx @@ -0,0 +1,26 @@ +import React, { useEffect } from 'react'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import { useAppContext } from 'src/context'; +import { ForgotPassword, Login } from 'src/pages/auth'; + +export const LoginRoutes = () => { + const { client } = useAppContext(); + const navigate = useNavigate(); + + useEffect(() => { + let route = '/login'; + + if (client) { + route = '/user/balance'; + } + + navigate(route); + }, [client]); + + return ( + + } /> + } /> + + ); +}; diff --git a/nym-browser-extension/src/routes/register/index.tsx b/nym-browser-extension/src/routes/register/index.tsx new file mode 100644 index 0000000000..27210900e6 --- /dev/null +++ b/nym-browser-extension/src/routes/register/index.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import { RegisterContextProvider } from 'src/context/register'; +import { ImportAccount, SeedPhrase, SetupComplete } from 'src/pages/register'; +import { CreatePasswordOnExistingAccount } from 'src/pages/register/CreatePasswordOnExistingAccount'; +import { CreatePasswordOnNewAccount } from 'src/pages/register/CreatePasswordOnNewAccount'; + +export const RegisterRoutes = () => { + const navigate = useNavigate(); + + const handleSetUpComplete = () => { + navigate('/login'); + }; + + return ( + + + navigate('/register/seed-phrase')} />} + /> + } /> + } /> + { + navigate('/register/complete'); + }} + /> + } + /> + } /> + + + ); +}; diff --git a/nym-browser-extension/src/routes/user/accounts/accounts.tsx b/nym-browser-extension/src/routes/user/accounts/accounts.tsx new file mode 100644 index 0000000000..63ef6e2abf --- /dev/null +++ b/nym-browser-extension/src/routes/user/accounts/accounts.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { RegisterContextProvider } from 'src/context/register'; +import { Accounts, AddAccount, ConfirmPassword, ImportAccount, NameAccount, SetupComplete } from 'src/pages'; + +export const AccountRoutes = () => ( + + + } /> + } /> + } /> + } /> + } /> + } /> + + +); diff --git a/nym-browser-extension/src/routes/user/index.tsx b/nym-browser-extension/src/routes/user/index.tsx new file mode 100644 index 0000000000..30e9ea1e11 --- /dev/null +++ b/nym-browser-extension/src/routes/user/index.tsx @@ -0,0 +1,25 @@ +import React, { useEffect } from 'react'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import { useAppContext } from 'src/context'; +import { Delegation, BalancePage, Receive, Send, Settings } from 'src/pages'; +import { AccountRoutes } from './accounts/accounts'; + +export const UserRoutes = () => { + const { client } = useAppContext(); + const navigate = useNavigate(); + + useEffect(() => { + if (!client) navigate('/login'); + }, [client]); + + return ( + + } /> + } /> + } /> + } /> + } /> + } /> + + ); +}; diff --git a/nym-browser-extension/src/theme/NymBrowserExtensionTheme.tsx b/nym-browser-extension/src/theme/NymBrowserExtensionTheme.tsx new file mode 100644 index 0000000000..dfde09cbc0 --- /dev/null +++ b/nym-browser-extension/src/theme/NymBrowserExtensionTheme.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { CssBaseline, PaletteMode } from '@mui/material'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { getDesignTokens } from './theme'; +import '@assets/fonts/non-variable/fonts.css'; + +type TNymBrowserExtThemeProps = { mode: PaletteMode; children: React.ReactNode }; + +export const NymBrowserExtThemeWithMode = ({ mode, children }: TNymBrowserExtThemeProps) => { + const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]); + + return ( + + + {children} + + ); +}; diff --git a/nym-browser-extension/src/theme/mui-theme.d.ts b/nym-browser-extension/src/theme/mui-theme.d.ts new file mode 100644 index 0000000000..6145e0d330 --- /dev/null +++ b/nym-browser-extension/src/theme/mui-theme.d.ts @@ -0,0 +1,66 @@ +/* eslint-disable no-shadow,@typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface */ +import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles'; +import { PaletteMode } from '@mui/material'; + +/** + * If you are unfamiliar with Material UI theming, please read the following first: + * - https://mui.com/customization/theming/ + * - https://mui.com/customization/palette/ + * - https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette + * + * This file adds typings to the theme using Typescript's module augmentation. + * + * Read the following if you are unfamiliar with module augmentation and declaration merging. Then + * look at the recommendations from Material UI docs for implementation: + * - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation + * - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces + * - https://mui.com/customization/palette/#adding-new-colors + * + * + * IMPORTANT: + * + * The type augmentation must match MUI's definitions. So, notice the use of `interface` rather than + * `type Foo = { ... }` - this is necessary to merge the definitions. + */ + +declare module '@mui/material/styles' { + /** + * This interface defines a palette used across Nym for branding + */ + interface NymPalette { + background: { light: string; dark: string }; + } + + interface NymPaletteVariant { + mode: PaletteMode; + } + + /** + * A palette definition only for the Nym Browser Extension that extends the Nym palette + */ + interface NymBrowserExtPalette { + nymBrowserExt: NymPaletteVariant; + } + + interface NymPaletteAndNymBrowserExtPalette { + nym: NymPalette & NymBrowserExtPalette; + } + + type NymPaletteAndNymBrowserExtPaletteOptions = Partial; + + /** + * Add anything not palette related to the theme here + */ + interface NymTheme {} + + /** + * This augments the definitions of the MUI Theme with the Nym theme, as well as + * a partial `ThemeOptions` type used by `createTheme` + * + * IMPORTANT: only add extensions to the interfaces above, do not modify the lines below + */ + interface Theme extends NymTheme {} + interface ThemeOptions extends Partial {} + interface Palette extends NymPaletteAndNymBrowserExtPalette {} + interface PaletteOptions extends NymPaletteAndNymBrowserExtPaletteOptions {} +} diff --git a/nym-browser-extension/src/theme/theme.ts b/nym-browser-extension/src/theme/theme.ts new file mode 100644 index 0000000000..b4ab3ee20e --- /dev/null +++ b/nym-browser-extension/src/theme/theme.ts @@ -0,0 +1,138 @@ +import { PaletteMode } from '@mui/material'; +import { + PaletteOptions, + NymPalette, + NymBrowserExtPalette, + ThemeOptions, + createTheme, + NymPaletteVariant, +} from '@mui/material/styles'; + +//----------------------------------------------------------------------------------------------- +// Nym palette type definitions +// + +/** + * The Nym palette. + * + * IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct + * colours for dark/light mode. + */ +const nymPalette: NymPalette = { + /** emphasises important elements */ + background: { light: '#F4F6F8', dark: '#1D2125' }, +}; + +const darkMode: NymPaletteVariant = { + mode: 'dark', +}; + +const lightMode: NymPaletteVariant = { + mode: 'light', +}; + +/** + * Nym palette specific to the Nym Wallet + * + * IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct + * colours for dark/light mode. + */ +const nymBrowserExtPalette = (variant: NymPaletteVariant): NymBrowserExtPalette => ({ + nymBrowserExt: variant, +}); + +//----------------------------------------------------------------------------------------------- +// Nym palettes for light and dark mode +// + +/** + * Map a Nym palette variant onto the MUI palette + */ +const variantToMUIPalette = (_: NymPaletteVariant): PaletteOptions => ({ + primary: { + main: '#6750A4', + }, +}); + +/** + * Returns the Network Explorer palette for light mode. + */ +const createLightModePalette = (): PaletteOptions => ({ + nym: { + ...nymPalette, + ...nymBrowserExtPalette(lightMode), + }, + ...variantToMUIPalette(lightMode), +}); + +/** + * Returns the Network Explorer palette for dark mode. + */ +const createDarkModePalette = (): PaletteOptions => ({ + nym: { + ...nymPalette, + ...nymBrowserExtPalette(darkMode), + }, + ...variantToMUIPalette(darkMode), +}); + +/** + * IMPORANT: if you need to get the default MUI theme, use the following + * + * import { createTheme as systemCreateTheme } from '@mui/system'; + * + * // get the MUI system defaults for light mode + * const systemTheme = systemCreateTheme({ palette: { mode: 'light' } }); + * + * + * return { + * // change `primary` to default MUI `success` + * primary: { + * main: systemTheme.palette.success.main, + * }, + * nym: { + * ...nymPalette, + * ...nymWalletPalette, + * }, + * }; + */ + +//----------------------------------------------------------------------------------------------- +// Nym theme overrides +// + +/** + * Gets the theme options to be passed to `createTheme`. + * + * Based on pattern from https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette. + * + * @param mode The theme mode: 'light' or 'dark' + */ +export const getDesignTokens = (mode: PaletteMode): ThemeOptions => { + // first, create the palette from user's choice of light or dark mode + const { palette } = createTheme({ + palette: { + mode, + ...(mode === 'light' ? createLightModePalette() : createDarkModePalette()), + }, + }); + + // then customise theme and components + return { + typography: { + fontFamily: [ + 'Open Sans', + 'sans-serif', + 'BlinkMacSystemFont', + 'Roboto', + 'Oxygen', + 'Ubuntu', + 'Helvetica Neue', + ].join(','), + }, + shape: { + borderRadius: 8, + }, + palette, + }; +}; diff --git a/nym-browser-extension/src/types/global.ts b/nym-browser-extension/src/types/global.ts new file mode 100644 index 0000000000..370aa18e22 --- /dev/null +++ b/nym-browser-extension/src/types/global.ts @@ -0,0 +1,5 @@ +export declare namespace NodeJS { + interface ProcessEnv { + development: string; + } +} diff --git a/nym-browser-extension/src/types/index.ts b/nym-browser-extension/src/types/index.ts new file mode 100644 index 0000000000..cbb355622f --- /dev/null +++ b/nym-browser-extension/src/types/index.ts @@ -0,0 +1 @@ +export * from './tx'; diff --git a/nym-browser-extension/src/types/tx.ts b/nym-browser-extension/src/types/tx.ts new file mode 100644 index 0000000000..afe264e119 --- /dev/null +++ b/nym-browser-extension/src/types/tx.ts @@ -0,0 +1,9 @@ +// TODO Add other transaction types later +type TTransactionType = 'send'; + +export type TTransaction = { + type: TTransactionType; + txHash?: string; + status: 'loading' | 'success' | 'error'; + message?: string; +}; diff --git a/nym-browser-extension/src/typings/jpeg.d.ts b/nym-browser-extension/src/typings/jpeg.d.ts new file mode 100644 index 0000000000..cfe3695fd5 --- /dev/null +++ b/nym-browser-extension/src/typings/jpeg.d.ts @@ -0,0 +1,4 @@ +declare module '*.jpeg' { + const value: any; + export default value; +} diff --git a/nym-browser-extension/src/typings/json.d.ts b/nym-browser-extension/src/typings/json.d.ts new file mode 100644 index 0000000000..b72dd46ee5 --- /dev/null +++ b/nym-browser-extension/src/typings/json.d.ts @@ -0,0 +1,4 @@ +declare module '*.json' { + const content: any; + export default content; +} diff --git a/nym-browser-extension/src/typings/png.d.ts b/nym-browser-extension/src/typings/png.d.ts new file mode 100644 index 0000000000..dd84df40a4 --- /dev/null +++ b/nym-browser-extension/src/typings/png.d.ts @@ -0,0 +1,4 @@ +declare module '*.png' { + const content: any; + export default content; +} diff --git a/nym-browser-extension/src/typings/svg.d.ts b/nym-browser-extension/src/typings/svg.d.ts new file mode 100644 index 0000000000..091d25e210 --- /dev/null +++ b/nym-browser-extension/src/typings/svg.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const content: any; + export default content; +} diff --git a/nym-browser-extension/src/urls/index.ts b/nym-browser-extension/src/urls/index.ts new file mode 100644 index 0000000000..10ab2d6aab --- /dev/null +++ b/nym-browser-extension/src/urls/index.ts @@ -0,0 +1,2 @@ +export const blockExplorerUrl = process.env.BLOCK_EXPLORER_URL; +export const coinGeckoPriceAPI = 'https://api.coingecko.com/api/v3/simple/price?'; diff --git a/nym-browser-extension/src/utils/coin.ts b/nym-browser-extension/src/utils/coin.ts new file mode 100644 index 0000000000..6b46d9b56e --- /dev/null +++ b/nym-browser-extension/src/utils/coin.ts @@ -0,0 +1,21 @@ +import Big from 'big.js'; + +export const unymToNym = (unym: number | Big, dp = 4) => { + let nym; + try { + nym = Big(unym).div(1_000_000).toFixed(dp); + } catch (e: any) { + console.warn(`${unym} not a valid decimal number: ${e}`); + } + return Number(nym); +}; + +export const nymToUnym = (nym: number) => { + let unym; + try { + unym = Big(nym).mul(1_000_000); + } catch (e: any) { + console.warn(`unable to convert nym to unym: ${e}`); + } + return Number(unym); +}; diff --git a/nym-browser-extension/src/utils/crypto.ts b/nym-browser-extension/src/utils/crypto.ts new file mode 100644 index 0000000000..122149ac60 --- /dev/null +++ b/nym-browser-extension/src/utils/crypto.ts @@ -0,0 +1,8 @@ +import cryptojs from 'crypto-js'; + +const encrypt = (mnemonic: string, password: string) => cryptojs.AES.encrypt(mnemonic, password).toString(); + +const decrypt = (cipher: string, password: string) => + cryptojs.AES.decrypt(cipher, password).toString(cryptojs.enc.Utf8); + +export { encrypt, decrypt }; diff --git a/nym-browser-extension/src/utils/fee.ts b/nym-browser-extension/src/utils/fee.ts new file mode 100644 index 0000000000..7357c6181f --- /dev/null +++ b/nym-browser-extension/src/utils/fee.ts @@ -0,0 +1,8 @@ +export const createFeeObject = (feeInUnyms?: number) => { + if (!feeInUnyms) return undefined; + + return { + amount: [{ amount: feeInUnyms.toString(), denom: 'unym' }], + gas: '100000', + }; +}; diff --git a/nym-browser-extension/src/utils/price.ts b/nym-browser-extension/src/utils/price.ts new file mode 100644 index 0000000000..8f38ca18db --- /dev/null +++ b/nym-browser-extension/src/utils/price.ts @@ -0,0 +1,18 @@ +import { coinGeckoPriceAPI } from 'src/urls'; + +export type Currency = 'gbp' | 'usd'; +export type TokenId = 'nym'; + +type ResponseMap = { [token in T]: { [currency in C]: number } }; + +const constructUrl = (tokenId: TokenId, currency: Currency) => + `${coinGeckoPriceAPI}ids=${tokenId}&vs_currencies=${currency}`; + +export async function getTokenPrice( + tokenId: T, + currency: C, +): Promise> { + const response = await fetch(constructUrl(tokenId, currency)); + const json = await response.json(); + return json; +} diff --git a/nym-browser-extension/src/validator-client/index.tsx b/nym-browser-extension/src/validator-client/index.tsx new file mode 100644 index 0000000000..0e007e3c26 --- /dev/null +++ b/nym-browser-extension/src/validator-client/index.tsx @@ -0,0 +1,15 @@ +import ValidatorClient from '@nymproject/nym-validator-client'; +import { config } from 'src/config'; + +export const generateMnemonmic = () => ValidatorClient.randomMnemonic(); + +export const connectToValidator = async (mnemonic: string) => + ValidatorClient.connect( + mnemonic, + config.rpcUrl, + config.validatorUrl, + config.prefix, + config.mixnetContractAddress, + config.vestingContractAddress, + config.denom, + ); diff --git a/nym-browser-extension/storage/.cargo/config.toml b/nym-browser-extension/storage/.cargo/config.toml new file mode 100644 index 0000000000..435ed755ec --- /dev/null +++ b/nym-browser-extension/storage/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" \ No newline at end of file diff --git a/nym-browser-extension/storage/.gitignore b/nym-browser-extension/storage/.gitignore new file mode 100644 index 0000000000..39e6cafa5c --- /dev/null +++ b/nym-browser-extension/storage/.gitignore @@ -0,0 +1,2 @@ +pkg +target \ No newline at end of file diff --git a/nym-browser-extension/storage/Cargo.lock b/nym-browser-extension/storage/Cargo.lock new file mode 100644 index 0000000000..be85c15aed --- /dev/null +++ b/nym-browser-extension/storage/Cargo.lock @@ -0,0 +1,814 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "argon2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +dependencies = [ + "base64ct", + "blake2", + "password-hash", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes", + "serde", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + +[[package]] +name = "cpufeatures" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "extension-storage" +version = "0.1.0" +dependencies = [ + "bip39", + "console_error_panic_hook", + "js-sys", + "serde-wasm-bindgen", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-utils", + "wee_alloc", + "zeroize", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "indexed_db_futures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfbcff6ae46750b15cc594bfd277b188cbddcfdc1817848f97f03f26f8625b9e" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "nym-store-cipher" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "argon2", + "generic-array", + "getrandom", + "rand", + "serde", + "serde_json", + "thiserror", + "zeroize", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "polyval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uuid" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +dependencies = [ + "getrandom", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "wasm-utils" +version = "0.1.0" +dependencies = [ + "futures", + "indexed_db_futures", + "js-sys", + "nym-store-cipher", + "serde", + "serde-wasm-bindgen", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] diff --git a/nym-browser-extension/storage/Cargo.toml b/nym-browser-extension/storage/Cargo.toml new file mode 100644 index 0000000000..dc48a6e7ef --- /dev/null +++ b/nym-browser-extension/storage/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "extension-storage" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/nymtech/nym" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +bip39 = { version = "2.0.0", features = ["zeroize"] } +js-sys = "*" +serde-wasm-bindgen = "0.5" +thiserror = "1.0.40" +wasm-bindgen = { version = "*" } # , features = ["serde-serialize"] } +wasm-bindgen-futures = "*" +zeroize = "1.6.0" + + +console_error_panic_hook = { version = "0.1", optional = true } + +# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size +# compared to the default allocator's ~10K. It is slower than the default +# allocator, however. +# +# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. +wee_alloc = { version = "0.4", optional = true } + +wasm-utils = { path = "../../common/wasm-utils", features = ["storage"] } + + +#[package.metadata.wasm-pack.profile.release] +#wasm-opt = false + +[features] +default = ["console_error_panic_hook"] \ No newline at end of file diff --git a/nym-browser-extension/storage/Makefile b/nym-browser-extension/storage/Makefile new file mode 100644 index 0000000000..c7d6657cd6 --- /dev/null +++ b/nym-browser-extension/storage/Makefile @@ -0,0 +1,2 @@ +wasm-pack: + wasm-pack build --scope nymproject \ No newline at end of file diff --git a/nym-browser-extension/storage/internal-dev/index.js b/nym-browser-extension/storage/internal-dev/index.js new file mode 100644 index 0000000000..ab69f6e686 --- /dev/null +++ b/nym-browser-extension/storage/internal-dev/index.js @@ -0,0 +1,73 @@ + +import { + ExtensionStorage, + set_panic_hook +} from "@nymproject/storage-extension" + +// // current limitation of rust-wasm for async stuff : ( +// let client = null + +async function main() { + // // sets up better stack traces in case of in-rust panics + set_panic_hook(); + + let storage = await new ExtensionStorage("my super duper password"); + + const goodMnemonic = "figure aspect pill salute review sponsor army city muffin engine army kid rival chunk unit insect blouse paddle velvet shallow box crawl grace never" + const badMnemonic = "foomp" + + let readEmpty = await storage.read_mnemonic("my-mnemonic1") + console.log("value initial:", readEmpty); + + try { + await storage.store_mnemonic("my-mnemonic1", badMnemonic); + } catch (e) { + console.log("store error: ",e) + } + + let anotherRead = await storage.read_mnemonic("my-mnemonic1") + console.log("value bad store:", anotherRead); + + await storage.store_mnemonic("my-mnemonic1", goodMnemonic) + + let yetAnotherRead = await storage.read_mnemonic("my-mnemonic1") + console.log("value good store:", yetAnotherRead); + + await storage.remove_mnemonic("my-mnemonic1") + + let finalRead = await storage.read_mnemonic("my-mnemonic1") + console.log("value removed:", finalRead); + + const anotherMnemonic = "salmon picture danger pill tomato hour hand chaos tray bargain frequent fuel scheme coil divert season lucky ginger mom stem mistake blanket lake suffer"; + const oneMore = "cat quiz circle letter trade unhappy quarter garlic sting gravity zone stock scatter merge account barrel forward fame club chest camp under crop connect" + + const key1 = "my-amazing-mnemonic" + const key2 = "my-other-mnemonic" + + await storage.store_mnemonic(key1, anotherMnemonic) + await storage.store_mnemonic(key2, oneMore) + + let allKeys = await storage.get_all_mnemonic_keys() + console.log("keys:", allKeys) + + const anotherOne = "mammal fashion rice two marble high brain achieve first harsh infant timber flush cloud hunt address brand immune tip identify aspect call beyond once" + const anotherKey = "some-mnemonic" + + let isPresent = await storage.has_mnemonic(anotherKey); + console.log("has mnemonic: ", isPresent) + + await storage.store_mnemonic(anotherKey, anotherOne) + + let isPresentNew = await storage.has_mnemonic(anotherKey); + console.log("has mnemonic: ", isPresentNew) + + await storage.remove_mnemonic(anotherKey) + + let isPresentEvenNewer = await storage.has_mnemonic(anotherKey); + console.log("has mnemonic: ", isPresentEvenNewer) + +} + + +// Let's get started! +main(); \ No newline at end of file diff --git a/nym-browser-extension/storage/src/error.rs b/nym-browser-extension/storage/src/error.rs new file mode 100644 index 0000000000..2ea167fa21 --- /dev/null +++ b/nym-browser-extension/storage/src/error.rs @@ -0,0 +1,37 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; +use wasm_bindgen::JsValue; +use wasm_utils::simple_js_error; +use wasm_utils::storage::error::StorageError; + +#[derive(Debug, Error)] +pub enum ExtensionStorageError { + #[error("serialization failure: {source}")] + JsonError { + #[from] + source: serde_wasm_bindgen::Error, + }, + + #[error("failed to use the storage: {source}")] + StorageError { + #[from] + source: StorageError, + }, + + #[error("there's already a stored mnemonic with name {name}")] + DuplicateMnemonic { name: String }, + + #[error("the provided mnemonic is malformed: {source}")] + InvalidMnemonic { + #[from] + source: bip39::Error, + }, +} + +impl From for JsValue { + fn from(value: ExtensionStorageError) -> Self { + simple_js_error(value.to_string()) + } +} diff --git a/nym-browser-extension/storage/src/lib.rs b/nym-browser-extension/storage/src/lib.rs new file mode 100644 index 0000000000..9f4ae1302b --- /dev/null +++ b/nym-browser-extension/storage/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use wasm_bindgen::prelude::*; + +pub mod error; +pub mod storage; + +pub use error::ExtensionStorageError; +pub use storage::ExtensionStorage; + +#[wasm_bindgen] +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/nym-browser-extension/storage/src/storage.rs b/nym-browser-extension/storage/src/storage.rs new file mode 100644 index 0000000000..9b7278d028 --- /dev/null +++ b/nym-browser-extension/storage/src/storage.rs @@ -0,0 +1,182 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ExtensionStorageError; +use js_sys::Promise; +use std::sync::Arc; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; +use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage}; +use wasm_utils::{check_promise_result, PromisableResult, PromisableResultError}; +use zeroize::Zeroizing; + +const STORAGE_NAME: &str = "nym-wallet-extension"; +const STORAGE_VERSION: u32 = 1; + +// v1 tables +mod v1 { + // stores + pub const MNEMONICS_STORE: &str = "mnemonics"; +} + +#[wasm_bindgen] +#[derive(Clone)] +pub struct ExtensionStorage { + inner: Arc, +} + +fn db_migration() -> Box Result<(), JsValue>> { + Box::new(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> { + // Even if the web-sys bindings expose the version as a f64, the IndexedDB API + // works with an unsigned integer. + // See + let old_version = evt.old_version() as u32; + + if old_version < 1 { + // migrating to version 1 + let db = evt.db(); + + db.create_object_store(v1::MNEMONICS_STORE)?; + } + + Ok(()) + }) +} + +#[wasm_bindgen] +impl ExtensionStorage { + pub(crate) async fn new_async(passphrase: String) -> Result { + // make sure the password is zeroized when no longer used, especially if we error out. + // special care must be taken on JS side to ensure it's correctly used there. + let passphrase = Zeroizing::new(passphrase); + + let pass_ref: &str = passphrase.as_ref(); + + let inner = WasmStorage::new( + STORAGE_NAME, + STORAGE_VERSION, + Some(db_migration()), + Some(pass_ref.as_bytes()), + ) + .await?; + + Ok(ExtensionStorage { + inner: Arc::new(inner), + }) + } + + #[wasm_bindgen(constructor)] + #[allow(clippy::new_ret_no_self)] + pub fn new(passphrase: String) -> Promise { + future_to_promise(async move { Self::new_async(passphrase).await.into_promise_result() }) + } + + pub(crate) async fn exists_async() -> Result { + Ok(WasmStorage::exists(STORAGE_NAME).await?) + } + + pub fn exists() -> Promise { + future_to_promise(async move { Self::exists_async().await.into_promise_result() }) + } + + async fn store_mnemonic_async( + &self, + name: String, + value: &bip39::Mnemonic, + ) -> Result<(), ExtensionStorageError> { + self.inner + .store_value(v1::MNEMONICS_STORE, JsValue::from_str(&name), value) + .await + .map_err(Into::into) + } + + async fn read_mnemonic_async( + &self, + name: String, + ) -> Result, ExtensionStorageError> { + self.inner + .read_value(v1::MNEMONICS_STORE, JsValue::from_str(&name)) + .await + .map_err(Into::into) + } + + async fn remove_mnemonic_async(&self, name: String) -> Result<(), ExtensionStorageError> { + self.inner + .remove_value(v1::MNEMONICS_STORE, JsValue::from_str(&name)) + .await + .map_err(Into::into) + } + + async fn has_mnemonic_async(&self, name: String) -> Result { + self.inner + .has_value(v1::MNEMONICS_STORE, JsValue::from_str(&name)) + .await + .map_err(Into::into) + } + + async fn get_all_mnemonic_keys_async(&self) -> Result { + self.inner + .get_all_keys(v1::MNEMONICS_STORE) + .await + .map_err(Into::into) + } + + #[wasm_bindgen] + pub fn store_mnemonic(&self, name: String, value: String) -> Promise { + let wrapped = Zeroizing::new(value); + let inner: &str = wrapped.as_ref(); + + let mnemonic = check_promise_result!( + bip39::Mnemonic::parse(inner).map_err(ExtensionStorageError::from) + ); + + // this clones the Arc pointer + let this = self.clone(); + future_to_promise(async move { + this.store_mnemonic_async(name, &mnemonic) + .await + .map(|_| JsValue::null()) + .map_promise_err() + }) + } + + #[wasm_bindgen] + pub fn read_mnemonic(&self, name: String) -> Promise { + // this clones the Arc pointer + let this = self.clone(); + future_to_promise(async move { + let maybe_mnemonic = this.read_mnemonic_async(name).await?; + Ok(serde_wasm_bindgen::to_value(&maybe_mnemonic)?) + }) + } + + #[wasm_bindgen] + pub fn remove_mnemonic(&self, name: String) -> Promise { + // this clones the Arc pointer + let this = self.clone(); + future_to_promise(async move { + this.remove_mnemonic_async(name) + .await + .map(|_| JsValue::null()) + .map_promise_err() + }) + } + + #[wasm_bindgen] + pub fn has_mnemonic(&self, name: String) -> Promise { + // this clones the Arc pointer + let this = self.clone(); + future_to_promise(async move { this.has_mnemonic_async(name).await.into_promise_result() }) + } + + #[wasm_bindgen] + pub fn get_all_mnemonic_keys(&self) -> Promise { + // this clones the Arc pointer + let this = self.clone(); + future_to_promise(async move { + this.get_all_mnemonic_keys_async() + .await + .into_promise_result() + }) + } +} diff --git a/nym-browser-extension/tsconfig.eslint.json b/nym-browser-extension/tsconfig.eslint.json new file mode 100644 index 0000000000..d1da615e58 --- /dev/null +++ b/nym-browser-extension/tsconfig.eslint.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "noEmit": true + }, + "include": [ + ".storybook/*.js", + "webpack.*.js", + "src/**/*.js", + "src/**/*.jsx", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.stories.*" + ], + "exclude": ["node_modules", "dist"] +} diff --git a/nym-browser-extension/tsconfig.json b/nym-browser-extension/tsconfig.json new file mode 100644 index 0000000000..213619ac53 --- /dev/null +++ b/nym-browser-extension/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": false, + "jsx": "react-jsx", + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@assets/*": ["../assets/*"] + }, + "noEmit": true + }, + "exclude": [ + "node_modules", + "dist", + "jest.config.js", + "webpack.config.js", + "webpack.prod.js", + "webpack.common.js", + "tsconfig.json" + ] +} diff --git a/nym-browser-extension/webpack.common.js b/nym-browser-extension/webpack.common.js new file mode 100644 index 0000000000..3ca6344af8 --- /dev/null +++ b/nym-browser-extension/webpack.common.js @@ -0,0 +1,26 @@ +const path = require('path'); +const { mergeWithRules } = require('webpack-merge'); +const { webpackCommon } = require('@nymproject/webpack'); + +module.exports = mergeWithRules({ + module: { + rules: { + test: 'match', + use: 'replace', + }, + }, +})(webpackCommon(__dirname), { + entry: path.resolve(__dirname, 'src/index.tsx'), + output: { + clean: true, + path: path.resolve(__dirname, 'dist'), + publicPath: '/', + }, + resolve: { + fallback: { + crypto: 'crypto-browserify', + stream: 'stream-browserify', + }, + }, + experiments: { asyncWebAssembly: true }, +}); diff --git a/nym-browser-extension/webpack.dev.js b/nym-browser-extension/webpack.dev.js new file mode 100644 index 0000000000..4554968e15 --- /dev/null +++ b/nym-browser-extension/webpack.dev.js @@ -0,0 +1,68 @@ +const { mergeWithRules } = require('webpack-merge'); +const webpack = require('webpack'); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); +const ReactRefreshTypeScript = require('react-refresh-typescript'); +const Dotenv = require('dotenv-webpack'); +const commonConfig = require('./webpack.common'); + +module.exports = mergeWithRules({ + module: { + rules: { + test: 'match', + use: 'replace', + }, + }, +})(commonConfig, { + mode: 'development', + devtool: 'inline-source-map', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + options: { + getCustomTransformers: () => ({ + before: [ReactRefreshTypeScript()], + }), + // `ts-loader` does not work with HMR unless `transpileOnly` is used. + // If you need type checking, `ForkTsCheckerWebpackPlugin` is an alternative. + transpileOnly: true, + }, + }, + ], + }, + plugins: [ + new ReactRefreshWebpackPlugin(), + + // this can be included automatically by the dev server, however build mode fails if missing + new webpack.HotModuleReplacementPlugin(), + new Dotenv({ path: './env.dev' }), + ], + + // recommended for faster rebuild + optimization: { + runtimeChunk: true, + removeAvailableModules: false, + removeEmptyChunks: false, + splitChunks: false, + }, + + cache: { + type: 'filesystem', + buildDependencies: { + // restart on config change + config: ['./webpack.dev.js'], + }, + }, + + devServer: { + port: 9000, + compress: true, + historyApiFallback: true, + hot: true, + client: { + overlay: false, + }, + }, +}); diff --git a/nym-browser-extension/webpack.prod.js b/nym-browser-extension/webpack.prod.js new file mode 100644 index 0000000000..359dc9f9a5 --- /dev/null +++ b/nym-browser-extension/webpack.prod.js @@ -0,0 +1,21 @@ +const path = require('path'); +const { default: merge } = require('webpack-merge'); +const common = require('./webpack.common'); +const CopyPlugin = require('copy-webpack-plugin'); +const Dotenv = require('dotenv-webpack'); + +module.exports = merge(common, { + mode: 'production', + entry: path.resolve(__dirname, 'src/index.tsx'), + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: './src/manifest.json', + to: './', + }, + ], + }), + new Dotenv({ path: './.env' }), + ], +}); diff --git a/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx index ac2132e636..555e8992bd 100644 --- a/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx @@ -14,7 +14,7 @@ import { createMnemonic, validateMnemonic } from 'src/requests'; import { Console } from 'src/utils/console'; import { AccountsContext } from 'src/context'; import { ConfirmPassword, Mnemonic } from 'src/components'; -import { MnemonicInput } from 'src/components/textfields'; +import { MnemonicInput } from '@nymproject/react/textfields/Mnemonic'; import { StyledBackButton } from 'src/components/StyledBackButton'; const createAccountSteps = [ diff --git a/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx b/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx index e2d6e02c2a..611f4b6869 100644 --- a/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx @@ -11,8 +11,9 @@ import { Typography, } from '@mui/material'; import { AccountsContext } from 'src/context'; -import { Mnemonic, PasswordInput } from 'src/components'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; import { StyledBackButton } from 'src/components/StyledBackButton'; +import { Mnemonic } from 'src/components/Mnemonic'; export const MnemonicModal = () => { const [password, setPassword] = useState(''); diff --git a/nym-wallet/src/components/Buy/Tutorial.tsx b/nym-wallet/src/components/Buy/Tutorial.tsx index be669a38a5..e0533e92b2 100644 --- a/nym-wallet/src/components/Buy/Tutorial.tsx +++ b/nym-wallet/src/components/Buy/Tutorial.tsx @@ -4,7 +4,8 @@ import { Tune as TuneIcon, BorderColor as BorderColorIcon } from '@mui/icons-mat import { CoinMark } from '@nymproject/react/coins/CoinMark'; import { PoweredByBity } from 'src/svg-icons'; import { AppContext } from 'src/context'; -import { NymCard, ClientAddress } from '..'; +import { ClientAddress } from '@nymproject/react/client-address/ClientAddress'; +import { NymCard } from '..'; import { SignMessageModal } from './SignMessageModal'; // TODO retrieve this value from env @@ -58,7 +59,12 @@ export const Tutorial = () => { const showBorder = useMediaQuery(theme.breakpoints.up('md')); return ( - }> + } + > Follow below 3 steps to quickly and easily buy NYM tokens. You can purchase up to 1000 Swiss Francs per day. diff --git a/nym-wallet/src/components/ConfirmPassword.tsx b/nym-wallet/src/components/ConfirmPassword.tsx index ba025fa1e6..ca191669c5 100644 --- a/nym-wallet/src/components/ConfirmPassword.tsx +++ b/nym-wallet/src/components/ConfirmPassword.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Button, CircularProgress, DialogActions, DialogContent, Typography } from '@mui/material'; import { useKeyPress } from 'src/hooks/useKeyPress'; -import { PasswordInput } from './textfields'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; import { StyledBackButton } from './StyledBackButton'; export const ConfirmPassword = ({ diff --git a/nym-wallet/src/components/Delegation/DelegationModal.tsx b/nym-wallet/src/components/Delegation/DelegationModal.tsx index adc4588c84..062002e1eb 100644 --- a/nym-wallet/src/components/Delegation/DelegationModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegationModal.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Typography, SxProps, Stack } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; -import { Console } from 'src/utils/console'; import { LoadingModal } from '../Modals/LoadingModal'; import { ConfirmationModal } from '../Modals/ConfirmationModal'; import { ErrorModal } from '../Modals/ErrorModal'; @@ -55,8 +54,6 @@ export const DelegationModal: FCWithChildren< ); } - transactions?.map((transaction) => Console.log('action', action, 'status', status, 'key', transaction.hash)); - return ( - } /> + + ) + } + /> void }) => { const [mnemonic, setMnemonic] = useState(''); diff --git a/nym-wallet/src/components/Settings/PasswordUpdateForm.tsx b/nym-wallet/src/components/Settings/PasswordUpdateForm.tsx index 04660c61c6..acfa5134aa 100644 --- a/nym-wallet/src/components/Settings/PasswordUpdateForm.tsx +++ b/nym-wallet/src/components/Settings/PasswordUpdateForm.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react'; import { Button, FormControl, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; import { updatePassword } from '../../requests'; import { PasswordStrength } from '../../pages/auth/components'; -import { PasswordInput } from '../textfields'; const PasswordUpdateForm = ({ onPwdSaved }: { onPwdSaved: () => void }) => { const [currentPassword, setCurrentPassword] = useState(''); diff --git a/nym-wallet/src/components/index.ts b/nym-wallet/src/components/index.ts index 690f9c9c86..4174b0c631 100644 --- a/nym-wallet/src/components/index.ts +++ b/nym-wallet/src/components/index.ts @@ -1,5 +1,4 @@ export * from './AppBar'; -export * from './ClientAddress'; export * from './ConfirmPassword'; export * from './CopyToClipboard'; export * from './ErrorFallback'; @@ -14,7 +13,6 @@ export * from './NymCard'; export * from './NymLogo'; export * from './RequestStatus'; export * from './SuccessResponse'; -export * from './textfields'; export * from './Title'; export * from './TokenPoolSelector'; export * from './TransactionDetails'; diff --git a/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx b/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx index 0460dfa71b..4601bfde22 100644 --- a/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx +++ b/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, Stack } from '@mui/material'; import { validateMnemonic } from 'src/requests'; -import { MnemonicInput } from 'src/components'; +import { MnemonicInput } from '@nymproject/react/textfields/Mnemonic'; import { AuthContext } from 'src/context/auth'; import { Subtitle } from '../components'; diff --git a/nym-wallet/src/pages/auth/pages/connect-password.tsx b/nym-wallet/src/pages/auth/pages/connect-password.tsx index 476f0a9aca..84ec88202f 100644 --- a/nym-wallet/src/pages/auth/pages/connect-password.tsx +++ b/nym-wallet/src/pages/auth/pages/connect-password.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Button, CircularProgress, FormControl, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; import { AuthContext } from 'src/context/auth'; -import { PasswordInput } from 'src/components'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; import { archiveWalletFile, createPassword, isPasswordCreated } from 'src/requests'; import { Subtitle, Title, PasswordStrength } from '../components'; diff --git a/nym-wallet/src/pages/auth/pages/create-password.tsx b/nym-wallet/src/pages/auth/pages/create-password.tsx index e071c6dcea..5df01b5250 100644 --- a/nym-wallet/src/pages/auth/pages/create-password.tsx +++ b/nym-wallet/src/pages/auth/pages/create-password.tsx @@ -4,7 +4,7 @@ import { Button, FormControl, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; import { AuthContext } from 'src/context/auth'; import { createPassword } from 'src/requests'; -import { PasswordInput } from 'src/components'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; import { Subtitle, Title, PasswordStrength } from '../components'; export const CreatePassword = () => { diff --git a/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx b/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx index 092b67a7a5..55f34774b5 100644 --- a/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx +++ b/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Box, Button, FormControl, Stack } from '@mui/material'; import { AppContext } from 'src/context'; import { isPasswordCreated } from 'src/requests'; -import { MnemonicInput } from 'src/components'; +import { MnemonicInput } from '@nymproject/react/textfields/Mnemonic'; import { Subtitle } from '../components'; export const SignInMnemonic = () => { diff --git a/nym-wallet/src/pages/auth/pages/signin-password.tsx b/nym-wallet/src/pages/auth/pages/signin-password.tsx index 6a61cff40a..0b62040ac6 100644 --- a/nym-wallet/src/pages/auth/pages/signin-password.tsx +++ b/nym-wallet/src/pages/auth/pages/signin-password.tsx @@ -1,7 +1,7 @@ import React, { useContext, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Box, Button, FormControl, Stack } from '@mui/material'; -import { PasswordInput } from 'src/components'; +import { PasswordInput } from '@nymproject/react/textfields/Password'; import { Subtitle } from '../components'; import { AppContext } from '../../../context/main'; diff --git a/nym-wallet/src/pages/balance/Balance.tsx b/nym-wallet/src/pages/balance/Balance.tsx index 1125076ee5..9ede7b6902 100644 --- a/nym-wallet/src/pages/balance/Balance.tsx +++ b/nym-wallet/src/pages/balance/Balance.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { Alert, Grid, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; +import { ClientAddress } from '@nymproject/react/client-address/ClientAddress'; import { Network } from 'src/types'; import { Balance } from '@nymproject/types'; -import { NymCard, ClientAddress } from '../../components'; +import { NymCard } from '../../components'; import { urls } from '../../context/main'; export const BalanceCard = ({ @@ -17,7 +18,12 @@ export const BalanceCard = ({ network?: Network; clientAddress?: string; }) => ( - }> + } + > {userBalanceError && ( diff --git a/package.json b/package.json index cad6eaf88d..523db5363f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "ts-packages/*", "nym-wallet", "nym-connect/**", + "nym-browser-extension", "explorer", "types", "clients/validator" @@ -28,8 +29,9 @@ "tsc": "lerna run tsc --stream", "types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app", "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run", - "preinstall": "yarn install:copy-placeholders", - "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm" + "preinstall": "yarn install:copy-placeholders && yarn install:copy-storage-placeholders", + "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm", + "install:copy-storage-placeholders": "mkdir -p nym-browser-extension/storage/pkg && cp scripts/build/yarn/storage-placeholder/package.json nym-browser-extension/storage/pkg" }, "devDependencies": { "lerna": "^6.6.2", diff --git a/scripts/build/yarn/storage-placeholder/package.json b/scripts/build/yarn/storage-placeholder/package.json new file mode 100644 index 0000000000..4a84a67b49 --- /dev/null +++ b/scripts/build/yarn/storage-placeholder/package.json @@ -0,0 +1,5 @@ +{ + "name": "@nymproject/extension-storage", + "version": "0.1.0", + "sideEffects": false +} diff --git a/ts-packages/mui-theme/package.json b/ts-packages/mui-theme/package.json index 7d30119d43..c1f33a412e 100644 --- a/ts-packages/mui-theme/package.json +++ b/ts-packages/mui-theme/package.json @@ -7,8 +7,8 @@ "peerDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "@mui/material": "^5.0.1", - "@mui/styles": "^5.0.1" + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2" }, "devDependencies": { "@nymproject/eslint-config-react-typescript": "^1.0.0", diff --git a/ts-packages/react-components/package.json b/ts-packages/react-components/package.json index 9ca96e8220..47fd5dd87c 100644 --- a/ts-packages/react-components/package.json +++ b/ts-packages/react-components/package.json @@ -30,6 +30,7 @@ "@mui/styles": ">= 5", "@mui/system": ">= 5", "@nymproject/mui-theme": "1", + "zxcvbn": "^4.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", "@nymproject/nym-validator-client": "^0.18.0", diff --git a/nym-wallet/src/components/ClientAddress.stories.tsx b/ts-packages/react-components/src/components/client-address/ClientAddress.stories.tsx similarity index 86% rename from nym-wallet/src/components/ClientAddress.stories.tsx rename to ts-packages/react-components/src/components/client-address/ClientAddress.stories.tsx index 08a3d873b4..304185826d 100644 --- a/nym-wallet/src/components/ClientAddress.stories.tsx +++ b/ts-packages/react-components/src/components/client-address/ClientAddress.stories.tsx @@ -23,6 +23,7 @@ export const WithCopy = Template.bind({}); WithCopy.args = { address: 'n222gnd9k6rytn6tz7pf8d2d4dawl7e9cr26111', withCopy: true, + smallIcons: true, }; export const WithLabel = Template.bind({}); @@ -46,3 +47,10 @@ EmptyWithLabelAndCopy.args = { withLabel: true, withCopy: true, }; + +export const WithSmallIcons = Template.bind({}); +WithSmallIcons.args = { + address: 'n222gnd9k6rytn6tz7pf8d2d4dawl7e9cr26111', + withCopy: true, + smallIcons: true, +}; diff --git a/nym-wallet/src/components/ClientAddress.tsx b/ts-packages/react-components/src/components/client-address/ClientAddress.tsx similarity index 60% rename from nym-wallet/src/components/ClientAddress.tsx rename to ts-packages/react-components/src/components/client-address/ClientAddress.tsx index eed426c12d..9fdb409c28 100644 --- a/nym-wallet/src/components/ClientAddress.tsx +++ b/ts-packages/react-components/src/components/client-address/ClientAddress.tsx @@ -1,8 +1,6 @@ -import React, { FC, useContext } from 'react'; +import React from 'react'; import { Box, Typography, Tooltip } from '@mui/material'; -import { AppContext } from '../context/main'; -import { CopyToClipboard } from './CopyToClipboard'; -import { splice } from '../utils'; +import { CopyToClipboard } from '../clipboard/CopyToClipboard'; const AddressTooltip: FCWithChildren<{ visible?: boolean; address?: string }> = ({ visible, address, children }) => { if (!visible || !address) { @@ -19,17 +17,20 @@ const AddressTooltip: FCWithChildren<{ visible?: boolean; address?: string }> = }; type ClientAddressProps = { + address: string; withLabel?: boolean; withCopy?: boolean; + smallIcons?: boolean; showEntireAddress?: boolean; }; -export const ClientAddressDisplay: FC = ({ +export const ClientAddressDisplay = ({ withLabel, withCopy, + smallIcons, showEntireAddress, address, -}) => ( +}: ClientAddressProps & { address?: string }) => ( {withLabel && ( <> @@ -40,15 +41,12 @@ export const ClientAddressDisplay: FC )} - - {showEntireAddress ? address || '' : splice(6, address)} + + {showEntireAddress ? address || '' : `${(address || '').slice(0, 6)}...${address.slice(-6)}`} - {withCopy && } + {withCopy && } ); -export const ClientAddress: FC = ({ ...props }) => { - const { clientDetails } = useContext(AppContext); - return ; -}; +export const ClientAddress = ({ ...props }: ClientAddressProps) => ; diff --git a/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx b/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx index 7050bd57ed..e02c5686d6 100644 --- a/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx +++ b/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx @@ -9,8 +9,9 @@ export const CopyToClipboard: FCWithChildren<{ value: string; tooltip?: React.ReactNode; onCopy?: (value: string) => void; + smallIcons?: boolean; sx?: SxProps; -}> = ({ value, tooltip, onCopy, sx }) => { +}> = ({ value, tooltip, onCopy, smallIcons, sx }) => { const copy = useClipboard(); const [showConfirmation, setShowConfirmation] = React.useState(false); const handleCopy = (e: React.MouseEvent) => { @@ -25,9 +26,13 @@ export const CopyToClipboard: FCWithChildren<{ return ( {showConfirmation ? ( - + ) : ( - + )} ); diff --git a/ts-packages/react-components/src/components/logo/NymLogoBW.tsx b/ts-packages/react-components/src/components/logo/NymLogoBW.tsx new file mode 100644 index 0000000000..4ca9ac644a --- /dev/null +++ b/ts-packages/react-components/src/components/logo/NymLogoBW.tsx @@ -0,0 +1,5 @@ +import * as React from 'react'; +import Logo from '@assets/logo/logo-bw.svg'; +import { LogoProps } from './LogoProps'; + +export const NymLogoBW = ({ height, width }: LogoProps) => ; diff --git a/ts-packages/react-components/src/components/password-strength/PasswordStrength.tsx b/ts-packages/react-components/src/components/password-strength/PasswordStrength.tsx new file mode 100644 index 0000000000..87ad9a3aa9 --- /dev/null +++ b/ts-packages/react-components/src/components/password-strength/PasswordStrength.tsx @@ -0,0 +1,95 @@ +/* eslint-disable no-nested-ternary */ +import React from 'react'; +import zxcvbn, { ZXCVBNScore } from 'zxcvbn'; +import { LockOutlined } from '@mui/icons-material'; +import { LinearProgress, Stack, Typography, Box } from '@mui/material'; + +const colorMap = { + 4: 'success' as 'success', + 3: 'success' as 'success', + 2: 'warning' as 'warning', + 1: 'error' as 'error', + 0: 'error' as 'error', +}; + +const getText = (score: ZXCVBNScore) => { + switch (score) { + case 4: + return 'Very strong password'; + case 3: + return 'Strong password'; + case 2: + return 'Average password'; + case 1: + return 'Weak password'; + case 0: + return 'Very weak password'; + default: + return ''; + } +}; + +const getColor = (score: ZXCVBNScore) => { + switch (score) { + case 4: + return 'success.main'; + case 3: + return 'success.main'; + case 2: + return 'warning.main'; + case 1: + return 'error.main'; + case 0: + return 'error.main'; + default: + return 'grey.500'; + } +}; + +const getPasswordStrength = (score: ZXCVBNScore) => { + switch (score) { + case 4: + return 100; + case 3: + return 75; + case 2: + return 50; + case 1: + return 25; + default: + return 0; + } +}; + +export const PasswordStrength = ({ + password, + withWarnings, + handleIsSafePassword, +}: { + password: string; + withWarnings?: boolean; + handleIsSafePassword: (isSafe: boolean) => void; +}) => { + const result = zxcvbn(password); + + handleIsSafePassword(result.score > 1); + + if (!password.length) return null; + + return ( + + + + + + + {getText(result.score)} + + + {withWarnings && result.feedback.warning && ( + {result.feedback.warning} + )} + + + ); +}; diff --git a/ts-packages/react-components/src/components/textfields/Mnemonic.tsx b/ts-packages/react-components/src/components/textfields/Mnemonic.tsx new file mode 100644 index 0000000000..ab40c0241f --- /dev/null +++ b/ts-packages/react-components/src/components/textfields/Mnemonic.tsx @@ -0,0 +1,37 @@ +import React, { useState } from 'react'; +import { Stack, TextField } from '@mui/material'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { Error } from '../warnings/Error'; + +export const MnemonicInput: FCWithChildren<{ + mnemonic: string; + error?: string; + onUpdateMnemonic: (mnemonic: string) => void; +}> = ({ mnemonic, error, onUpdateMnemonic }) => { + const [showMnemonic, setShowMnemonic] = useState(false); + return ( + + onUpdateMnemonic(e.target.value)} + autoFocus + fullWidth + multiline={showMnemonic} + InputLabelProps={{ shrink: true }} + sx={{ + 'input::-webkit-textfield-decoration-container': { + alignItems: 'start', + }, + }} + /> + setShowMnemonic((show) => !show)} />} + label="Reveal my mnemonic" + /> + {error && } + + ); +}; diff --git a/nym-wallet/src/components/textfields.tsx b/ts-packages/react-components/src/components/textfields/Password.tsx similarity index 53% rename from nym-wallet/src/components/textfields.tsx rename to ts-packages/react-components/src/components/textfields/Password.tsx index 94cc2577a6..fde4ebb6c5 100644 --- a/nym-wallet/src/components/textfields.tsx +++ b/ts-packages/react-components/src/components/textfields/Password.tsx @@ -1,41 +1,7 @@ import React, { useState } from 'react'; import { Box, IconButton, Stack, TextField } from '@mui/material'; import { Visibility, VisibilityOff } from '@mui/icons-material'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Checkbox from '@mui/material/Checkbox'; -import { Error } from './Error'; - -export const MnemonicInput: FCWithChildren<{ - mnemonic: string; - error?: string; - onUpdateMnemonic: (mnemonic: string) => void; -}> = ({ mnemonic, error, onUpdateMnemonic }) => { - const [showMnemonic, setShowMnemonic] = useState(false); - return ( - - onUpdateMnemonic(e.target.value)} - autoFocus - fullWidth - multiline={showMnemonic} - InputLabelProps={{ shrink: true }} - sx={{ - 'input::-webkit-textfield-decoration-container': { - alignItems: 'start', - }, - }} - /> - setShowMnemonic((show) => !show)} />} - label="Reveal my mnemonic" - /> - {error && } - - ); -}; +import { Error } from '../warnings/Error'; export const PasswordInput: FCWithChildren<{ password: string; diff --git a/ts-packages/react-components/src/components/warnings/Error.tsx b/ts-packages/react-components/src/components/warnings/Error.tsx new file mode 100644 index 0000000000..0101291cf6 --- /dev/null +++ b/ts-packages/react-components/src/components/warnings/Error.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Alert } from '@mui/material'; + +export const Error = ({ message }: { message: string }) => ( + + {message} + +); diff --git a/yarn.lock b/yarn.lock index 2eb29a1a15..c223bce5af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1640,6 +1640,23 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@emotion/babel-plugin@^11.10.6": + version "11.10.6" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz#a68ee4b019d661d6f37dec4b8903255766925ead" + integrity sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.0" + "@emotion/memoize" "^0.8.0" + "@emotion/serialize" "^1.1.1" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.1.3" + "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" @@ -1668,19 +1685,19 @@ "@emotion/weak-memoize" "^0.3.1" stylis "4.2.0" -"@emotion/hash@^0.9.1": +"@emotion/hash@^0.9.0", "@emotion/hash@^0.9.1": version "0.9.1" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== -"@emotion/is-prop-valid@^1.2.1": +"@emotion/is-prop-valid@^1.2.0", "@emotion/is-prop-valid@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== dependencies: "@emotion/memoize" "^0.8.1" -"@emotion/memoize@^0.8.1": +"@emotion/memoize@^0.8.0", "@emotion/memoize@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== @@ -1699,7 +1716,7 @@ "@emotion/weak-memoize" "^0.3.1" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.2": +"@emotion/serialize@^1.1.1", "@emotion/serialize@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== @@ -1727,17 +1744,29 @@ "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" "@emotion/utils" "^1.2.1" +"@emotion/styled@^11.7.0": + version "11.10.6" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.6.tgz#d886afdc51ef4d66c787ebde848f3cc8b117ebba" + integrity sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.10.6" + "@emotion/is-prop-valid" "^1.2.0" + "@emotion/serialize" "^1.1.1" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" + "@emotion/utils" "^1.2.0" + "@emotion/unitless@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": +"@emotion/use-insertion-effect-with-fallbacks@^1.0.0", "@emotion/use-insertion-effect-with-fallbacks@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== -"@emotion/utils@^1.2.1": +"@emotion/utils@^1.2.0", "@emotion/utils@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== @@ -1829,6 +1858,11 @@ resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-2.9.11.tgz#9ce96e7746625a89239f68ca57c4f654264c17ef" integrity sha512-bA3aZ79UgcHj7tFV7RlgThzwSSHZgvfbt2wprldRkYBcMopdMvHyO17Wwp/twcJasNFischFfS7oz8Katz8DdQ== +"@hookform/resolvers@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.1.0.tgz#ff83ef4aa6078173201da131ceea4c3583b67034" + integrity sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA== + "@humanwhocodes/config-array@^0.11.10": version "0.11.10" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" @@ -2381,6 +2415,20 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@mui/base@5.0.0-beta.0": + version "5.0.0-beta.0" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.0.tgz#4145f8a700e04d68d703da70d3c26c62d278f4e5" + integrity sha512-ap+juKvt8R8n3cBqd/pGtZydQ4v2I/hgJKnvJRGjpSh3RvsvnDHO4rXov8MHQlH6VqpOekwgilFLGxMZjNTucA== + dependencies: + "@babel/runtime" "^7.21.0" + "@emotion/is-prop-valid" "^1.2.0" + "@mui/types" "^7.2.4" + "@mui/utils" "^5.12.3" + "@popperjs/core" "^2.11.7" + clsx "^1.2.1" + prop-types "^15.8.1" + react-is "^18.2.0" + "@mui/base@5.0.0-beta.4": version "5.0.0-beta.4" resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.4.tgz#e3f4f4a056b88ab357194a245e223177ce35e0b0" @@ -2409,6 +2457,11 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/core-downloads-tracker@^5.13.0": + version "5.13.7" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.7.tgz#f4d9af5fe113b80b98b2cb158263d7b8f77e61c7" + integrity sha512-/suIo4WoeL/OyO3KUsFVpdOmKiSAr6NpWXmQ4WLSxwKrTiha1FJxM6vwAki5W/5kR9WnVLw5E8JC4oHHsutT8w== + "@mui/core-downloads-tracker@^5.13.4": version "5.13.4" resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz#7e4b491d8081b6d45ae51556d82cb16b31315a19" @@ -2421,6 +2474,13 @@ dependencies: "@babel/runtime" "^7.21.0" +"@mui/icons-material@^5.11.11": + version "5.11.11" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.11.11.tgz#d4e01bd405b0dac779f5e060586277f91f3acb6e" + integrity sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw== + dependencies: + "@babel/runtime" "^7.21.0" + "@mui/lab@^5.0.0-alpha.72": version "5.0.0-alpha.134" resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.134.tgz#e48c108fce91fbb89446dcf86ca35e7e761bc078" @@ -2453,13 +2513,31 @@ react-is "^18.2.0" react-transition-group "^4.4.5" -"@mui/private-theming@^5.13.1": - version "5.13.1" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.13.1.tgz#c3e9a0b44f9c5a51b92cfcfb660536060cb61ed7" - integrity sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ== +"@mui/material@^5.11.15": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.13.0.tgz#23c63a9d117dca22b81bb82db6e9c4d761c8fbf1" + integrity sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw== dependencies: "@babel/runtime" "^7.21.0" - "@mui/utils" "^5.13.1" + "@mui/base" "5.0.0-beta.0" + "@mui/core-downloads-tracker" "^5.13.0" + "@mui/system" "^5.12.3" + "@mui/types" "^7.2.4" + "@mui/utils" "^5.12.3" + "@types/react-transition-group" "^4.4.6" + clsx "^1.2.1" + csstype "^3.1.2" + prop-types "^15.8.1" + react-is "^18.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^5.13.1", "@mui/private-theming@^5.13.7": + version "5.13.7" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.13.7.tgz#2f8ef5da066f3c6c6423bd4260d003a28d10b099" + integrity sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA== + dependencies: + "@babel/runtime" "^7.22.5" + "@mui/utils" "^5.13.7" prop-types "^15.8.1" "@mui/styled-engine@^5.13.2": @@ -2509,6 +2587,20 @@ csstype "^3.1.2" prop-types "^15.8.1" +"@mui/system@^5.11.15", "@mui/system@^5.12.3": + version "5.13.7" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.13.7.tgz#b02e6284bbaab4201b142546ebbb2012ec0fa63d" + integrity sha512-7R2KdI6vr8KtnauEfg9e9xQmPk6Gg/1vGNiALYyhSI+cYztxN6WmlSqGX4bjWn/Sygp1TUE1jhFEgs7MWruhkQ== + dependencies: + "@babel/runtime" "^7.22.5" + "@mui/private-theming" "^5.13.7" + "@mui/styled-engine" "^5.13.2" + "@mui/types" "^7.2.4" + "@mui/utils" "^5.13.7" + clsx "^1.2.1" + csstype "^3.1.2" + prop-types "^15.8.1" + "@mui/types@^7.2.4": version "7.2.4" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328" @@ -2525,6 +2617,17 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/utils@^5.12.3", "@mui/utils@^5.13.7": + version "5.13.7" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.13.7.tgz#7e6a8336e05eb2642667a5c02eb605351e27ec20" + integrity sha512-/3BLptG/q0u36eYED7Nhf4fKXmcKb6LjjT7ZMwhZIZSdSxVqDqSTmATW3a56n3KEPQUXCU9TpxAfCBQhs6brVA== + dependencies: + "@babel/runtime" "^7.22.5" + "@types/prop-types" "^15.7.5" + "@types/react-is" "^18.2.1" + prop-types "^15.8.1" + react-is "^18.2.0" + "@mui/x-data-grid@^5.0.0-beta.5": version "5.17.26" resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-5.17.26.tgz#1f7fa73dd3986cf052e2fd2cb56eb4678a7bd913" @@ -2827,6 +2930,9 @@ dependencies: nx "15.9.4" +"@nymproject/extension-storage@file:./nym-browser-extension/storage/pkg": + version "0.1.0" + "@nymproject/nym-validator-client@^0.18.0": version "0.18.0" resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.18.0.tgz#4dd72bafdf6c72b603242f32c0bb9a1f9e475b98" @@ -2999,7 +3105,7 @@ schema-utils "^3.0.0" source-map "^0.7.3" -"@popperjs/core@^2.11.8": +"@popperjs/core@^2.11.7", "@popperjs/core@^2.11.8": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== @@ -3062,6 +3168,11 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.0.tgz#550a8d5760b78efc5d60f62b5829b0f74c1fde81" integrity sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw== +"@remix-run/router@1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.1.tgz#fea7ac35ae4014637c130011f59428f618730498" + integrity sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ== + "@rollup/plugin-commonjs@^24.0.1": version "24.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-24.1.0.tgz#79e54bd83bb64396761431eee6c44152ef322100" @@ -4137,7 +4248,7 @@ react-docgen-typescript "^2.1.1" tslib "^2.0.0" -"@storybook/react@^6.5.15": +"@storybook/react@^6.5.15", "@storybook/react@^6.5.16": version "6.5.16" resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.5.16.tgz#f7b82ba87f5bb73b4e4e83cce298a98710a88398" integrity sha512-cBtNlOzf/MySpNLBK22lJ8wFU22HnfTB2xJyBk7W7Zi71Lm7Uxkhv1Pz8HdiQndJ0SlsAAQOWjQYsSZsGkZIaA== @@ -4504,6 +4615,20 @@ lz-string "^1.5.0" pretty-format "^27.0.2" +"@testing-library/dom@^9.0.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.2.0.tgz#0e1f45e956f2a16f471559c06edd8827c4832f04" + integrity sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + "@testing-library/jest-dom@^5.14.1": version "5.16.5" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" @@ -4528,6 +4653,15 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "<18.0.0" +"@testing-library/react@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" + integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^9.0.0" + "@types/react-dom" "^18.0.0" + "@testing-library/user-event@^13.2.1": version "13.5.0" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" @@ -4670,6 +4804,11 @@ dependencies: "@types/node" "*" +"@types/crypto-js@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" + integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== + "@types/d3-array@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.5.tgz#857c1afffd3f51319bbc5b301956aca68acaa7b8" @@ -5036,6 +5175,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.37.tgz#a1f8728e4dc30163deb41e9b7aba65d0c2d4eda1" integrity sha512-ql+4dw4PlPFBP495k8JzUX/oMNRI2Ei4PrMHgj8oT4VhGlYUzF4EYr0qk2fW+XBVGIrq8Zzk13m4cvyXZuv4pA== +"@types/node@^18.16.1": + version "18.16.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.7.tgz#86d0ba2541f808cb78d4dc5d3e40242a349d6db8" + integrity sha512-MFg7ua/bRtnA1hYE3pVyWxGd/r7aMqjNOdHvlSsXV3n8iaeGKkOaPzpJh6/ovf4bEXWcojkeMJpTsq3mzXW4IQ== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -5095,6 +5239,13 @@ dependencies: "@types/react" "^17" +"@types/react-dom@^18.0.0": + version "18.2.4" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.4.tgz#13f25bfbf4e404d26f62ac6e406591451acba9e0" + integrity sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw== + dependencies: + "@types/react" "*" + "@types/react-dom@^18.0.10": version "18.2.6" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.6.tgz#ad621fa71a8db29af7c31b41b2ea3d8a6f4144d1" @@ -5102,7 +5253,7 @@ dependencies: "@types/react" "*" -"@types/react-is@^18.2.0": +"@types/react-is@^18.2.0", "@types/react-is@^18.2.1": version "18.2.1" resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-18.2.1.tgz#61d01c2a6fc089a53520c0b66996d458fdc46863" integrity sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw== @@ -5682,6 +5833,11 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== +"@webpack-cli/configtest@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.0.1.tgz#a69720f6c9bad6aef54a8fa6ba9c3533e7ef4c7f" + integrity sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A== + "@webpack-cli/info@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" @@ -5689,11 +5845,21 @@ dependencies: envinfo "^7.7.3" +"@webpack-cli/info@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0" + integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA== + "@webpack-cli/serve@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== +"@webpack-cli/serve@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.2.tgz#10aa290e44a182c02e173a89452781b1acbc86d9" + integrity sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -7566,6 +7732,11 @@ commander@2, commander@^2.19.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -7823,6 +7994,18 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + core-js-compat@^3.30.1, core-js-compat@^3.30.2, core-js-compat@^3.8.1: version "3.31.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" @@ -7992,6 +8175,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -8869,6 +9057,13 @@ dotenv-webpack@^7.0.3: dependencies: dotenv-defaults "^2.0.2" +dotenv-webpack@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz#6656550460a8076fab20e5ac2eac867e72478645" + integrity sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w== + dependencies: + dotenv-defaults "^2.0.2" + dotenv@^16.0.3: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" @@ -9582,6 +9777,11 @@ estree-util-visit@^1.0.0: "@types/estree-jsx" "^1.0.0" "@types/unist" "^2.0.0" +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + estree-walker@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" @@ -9858,6 +10058,17 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.2.11, fast-glob@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" + integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" @@ -10556,7 +10767,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^6.0.2: +glob-parent@^6.0.1, glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -10687,6 +10898,17 @@ globby@11.1.0, globby@^11.0.1, globby@^11.0.2, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +globby@^13.1.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -11290,7 +11512,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.4, ignore@^5.2.0: +ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -11450,6 +11672,11 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -11510,7 +11737,7 @@ is-alphanumerical@^2.0.0: is-alphabetical "^2.0.0" is-decimal "^2.0.0" -is-arguments@^1.1.1: +is-arguments@^1.0.4, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -11701,6 +11928,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^3.0.0, is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -11922,7 +12156,7 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typed-array@^1.1.10, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== @@ -13393,6 +13627,13 @@ lz-string@^1.5.0: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== +magic-string@^0.25.3: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + magic-string@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" @@ -16174,9 +16415,9 @@ prettier-linter-helpers@^1.0.0: integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== prettier@^2.8.7: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + version "2.8.7" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" + integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== pretty-bytes@^6.0.0: version "6.1.0" @@ -16493,6 +16734,11 @@ qrcode.react@^1.0.1: prop-types "^15.6.0" qr.js "0.0.0" +qrcode.react@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" + integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -16640,6 +16886,13 @@ react-error-boundary@^3.1.3, react-error-boundary@^3.1.4: dependencies: "@babel/runtime" "^7.12.5" +react-error-boundary@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.4.tgz#d2e84505b0a67cec7a6bf33b0146faadfe31597d" + integrity sha512-AbqMFx8bCsob8rCHZvJYQ42MQijK0/034RUvan9qrqyJCpazr8d9vKHrysbxcr6odoHLZvQEcYomFPoIqH9fow== + dependencies: + "@babel/runtime" "^7.12.5" + react-google-charts@^3.0.15: version "3.0.15" resolved "https://registry.yarnpkg.com/react-google-charts/-/react-google-charts-3.0.15.tgz#30759a470f48336e744fd383d054122b039a1ff2" @@ -16647,7 +16900,7 @@ react-google-charts@^3.0.15: dependencies: react-load-script "^0.0.6" -react-hook-form@^7.14.2: +react-hook-form@^7.14.2, react-hook-form@^7.43.9: version "7.45.1" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.1.tgz#e352c7f4dbc7540f0756abbb4dcfd1122fecc9bb" integrity sha512-6dWoFJwycbuFfw/iKMcl+RdAOAOHDiF11KWYhNDRN/OkUt+Di5qsZHwA0OwsVnu9y135gkHpTw9DJA+WzCeR9w== @@ -16722,6 +16975,11 @@ react-refresh-typescript@^2.0.2, react-refresh-typescript@^2.0.3: resolved "https://registry.yarnpkg.com/react-refresh-typescript/-/react-refresh-typescript-2.0.9.tgz#f8a86efcb34f8d717100230564b9b57477d74b10" integrity sha512-chAnOO4vpxm/3WkgOVmti+eN8yUtkJzeGkOigV6UA9eDFz12W34e/SsYe2H5+RwYJ3+sfSZkVbiXcG1chEBxlg== +react-refresh-typescript@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/react-refresh-typescript/-/react-refresh-typescript-2.0.8.tgz#84d086f9da742d7de87ad15824301763bf8135ba" + integrity sha512-7zsaM8jIpUZ2hxMdTo9GNZRauTI11AD5YNNaNXVGb8mtYVwdSbc6IQV8YaHevSWYbx+ZpZispezdCfaJuTrh3w== + react-refresh@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" @@ -16732,6 +16990,11 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== + react-resize-detector@^8.0.4: version "8.1.0" resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-8.1.0.tgz#1c7817db8bc886e2dbd3fbe3b26ea8e56be0524a" @@ -16747,6 +17010,14 @@ react-router-dom@6, react-router-dom@^6.7.0: "@remix-run/router" "1.7.0" react-router "6.14.0" +react-router-dom@^6.9.0: + version "6.14.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.1.tgz#0ad7ba7abdf75baa61169d49f096f0494907a36f" + integrity sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw== + dependencies: + "@remix-run/router" "1.7.1" + react-router "6.14.1" + react-router@6.14.0: version "6.14.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.0.tgz#1c3e8e922d934d43a253fd862c72c82167c0a7f1" @@ -16754,6 +17025,13 @@ react-router@6.14.0: dependencies: "@remix-run/router" "1.7.0" +react-router@6.14.1: + version "6.14.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.1.tgz#5e82bcdabf21add859dc04b1859f91066b3a5810" + integrity sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g== + dependencies: + "@remix-run/router" "1.7.1" + react-simple-maps@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-simple-maps/-/react-simple-maps-2.3.0.tgz#7fdb55120335a9b1ef5bf62e0dcb669511705f3e" @@ -17013,6 +17291,13 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -17399,11 +17684,34 @@ rollup-plugin-dts@^5.0.0, rollup-plugin-dts@^5.2.0: optionalDependencies: "@babel/code-frame" "^7.18.6" +rollup-plugin-inject@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz#e4233855bfba6c0c12a312fd6649dff9a13ee9f4" + integrity sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w== + dependencies: + estree-walker "^0.6.1" + magic-string "^0.25.3" + rollup-pluginutils "^2.8.1" + +rollup-plugin-node-polyfills@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz#53092a2744837164d5b8a28812ba5f3ff61109fd" + integrity sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA== + dependencies: + rollup-plugin-inject "^3.0.0" + rollup-plugin-web-worker-loader@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/rollup-plugin-web-worker-loader/-/rollup-plugin-web-worker-loader-1.6.1.tgz#9d7a27575b64b0780fe4e8b3bc87470d217e485f" integrity sha512-4QywQSz1NXFHKdyiou16mH3ijpcfLtLGOrAqvAqu1Gx+P8+zj+3gwC2BSL/VW1d+LW4nIHC8F7d7OXhs9UdR2A== +rollup-pluginutils@^2.8.1: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + rollup@^3.17.2, rollup@^3.2.1, rollup@^3.9.1: version "3.25.3" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.25.3.tgz#f9a8986f0f244bcfde2208da91ba46b8fd252551" @@ -17892,6 +18200,11 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -18023,6 +18336,11 @@ source-map@^0.7.0, source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -18463,6 +18781,11 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" +stylis@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" + integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== + stylis@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" @@ -19586,6 +19909,17 @@ util@^0.11.0: dependencies: inherits "2.0.3" +util@^0.12.5: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -19880,6 +20214,25 @@ webpack-cli@^4.8.0: rechoir "^0.7.0" webpack-merge "^5.7.3" +webpack-cli@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.2.tgz#2954c10ecb61c5d4dad6f68ee2d77f051741946c" + integrity sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^2.0.1" + "@webpack-cli/info" "^2.0.1" + "@webpack-cli/serve" "^2.0.2" + colorette "^2.0.14" + commander "^10.0.1" + cross-spawn "^7.0.3" + envinfo "^7.7.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^3.1.1" + rechoir "^0.8.0" + webpack-merge "^5.7.3" + webpack-dev-middleware@^3.7.3: version "3.7.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" @@ -20135,7 +20488,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.9: +which-typed-array@^1.1.2, which-typed-array@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== @@ -20474,6 +20827,11 @@ yup@^0.32.9: property-expr "^2.0.4" toposort "^2.0.2" +zod@^3.21.4: + version "3.21.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" + integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"