diff --git a/CHANGELOG.md b/CHANGELOG.md index 0840f92d..63952dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +# 0.4.3 +- [#49](https://github.com/blockcoders/kuma-wallet/pull/49) Move state to background + # 0.4.3 - [#48](https://github.com/blockcoders/kuma-wallet/pull/48) New Logos and interface diff --git a/README-es.md b/README-es.md index 64b83dd0..602b6877 100644 --- a/README-es.md +++ b/README-es.md @@ -2,7 +2,7 @@ Kuma Wallet ===========

- +

[![CircleCI](https://dl.circleci.com/status-badge/img/gh/kumawallet/extension/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/kumawallet/extension/tree/main) diff --git a/README.md b/README.md index b6e1c0cf..6de2fe1d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Kuma Wallet ===========

- +

[![CircleCI](https://dl.circleci.com/status-badge/img/gh/kumawallet/extension/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/kumawallet/extension/tree/main) diff --git a/images/logo.svg b/images/logo.svg deleted file mode 100644 index dd5a1822..00000000 --- a/images/logo.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/package-lock.json b/package-lock.json index 415a6f2e..19285148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kuma-wallet", - "version": "0.4.3", + "version": "0.4.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kuma-wallet", - "version": "0.4.3", + "version": "0.4.4", "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.18", @@ -14,10 +14,11 @@ "@metamask/browser-passworder": "^4.3.0", "@polkadot/api": "10.11.2", "@polkadot/api-contract": "10.11.2", + "@polkadot/extension-base": "^0.46.6", "@polkadot/ui-keyring": "3.6.4", "@polkadot/util": "12.6.2", "@polkadot/util-crypto": "12.6.2", - "@sentry/react": "^7.99.0", + "@sentry/react": "^7.100.1", "@types/lodash.debounce": "^4.0.9", "axios": "^1.6.7", "ethers": "^5.7.2", @@ -30,29 +31,31 @@ "react": "^18.2.0", "react-dnd-multi-backend": "^8.0.3", "react-dom": "^18.2.0", - "react-hook-form": "^7.50.0", - "react-i18next": "^14.0.1", + "react-hook-form": "^7.50.1", + "react-i18next": "^14.0.5", "react-icons": "^5.0.1", "react-number-format": "^5.3.1", "react-qr-code": "^2.0.12", "react-router-dom": "^6.22.0", "react-toastify": "^10.0.4", - "virtua": "^0.23.2", + "uuid": "^9.0.1", + "virtua": "^0.24.0", "yup": "^0.32.11" }, "devDependencies": { "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@polkadot/types-codec": "10.11.2", - "@testing-library/jest-dom": "^6.4.1", + "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", "@types/chrome": "^0.0.260", "@types/node": "^20.11.16", "@types/randomcolor": "^0.5.9", - "@types/react": "^18.2.52", + "@types/react": "^18.2.55", "@types/react-dom": "^18.2.18", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.2.1", "@vitest/coverage-v8": "^1.2.2", "autoprefixer": "^10.4.17", @@ -66,7 +69,7 @@ "fs-extra": "^11.2.0", "jsdom": "^24.0.0", "nodemon": "^3.0.3", - "postcss": "^8.4.33", + "postcss": "^8.4.34", "rollup-plugin-polyfill-node": "^0.13.0", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", @@ -2259,6 +2262,91 @@ "node": ">=18" } }, + "node_modules/@polkadot/extension-base": { + "version": "0.46.6", + "resolved": "https://registry.npmjs.org/@polkadot/extension-base/-/extension-base-0.46.6.tgz", + "integrity": "sha512-VKCI2QyBjCdBU9DzlJm7iBXjG6x+gsdhbtKtXDYLufat1Ng6ZTxrw2T+CfPD/ZXEd3bOkOGr21N+Tb90AD8JDQ==", + "dependencies": { + "@polkadot/api": "^10.11.1", + "@polkadot/extension-chains": "0.46.6", + "@polkadot/extension-dapp": "0.46.6", + "@polkadot/extension-inject": "0.46.6", + "@polkadot/keyring": "^12.6.1", + "@polkadot/networks": "^12.6.1", + "@polkadot/phishing": "^0.22.1", + "@polkadot/rpc-provider": "^10.11.1", + "@polkadot/types": "^10.11.1", + "@polkadot/ui-keyring": "^3.6.4", + "@polkadot/ui-settings": "^3.6.4", + "@polkadot/util": "^12.6.1", + "@polkadot/util-crypto": "^12.6.1", + "eventemitter3": "^5.0.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/extension-chains": { + "version": "0.46.6", + "resolved": "https://registry.npmjs.org/@polkadot/extension-chains/-/extension-chains-0.46.6.tgz", + "integrity": "sha512-SoYZ7q6naWvIhXoKH38ZAm5W9sSENDGypb28417Z7U3PUH/b/FVi6NU46upxlmaJgVo57zHxOQpSCT7gS7kYdQ==", + "dependencies": { + "@polkadot/extension-inject": "0.46.6", + "@polkadot/networks": "^12.6.1", + "@polkadot/util": "^12.6.1", + "@polkadot/util-crypto": "^12.6.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/api": "*", + "@polkadot/types": "*" + } + }, + "node_modules/@polkadot/extension-dapp": { + "version": "0.46.6", + "resolved": "https://registry.npmjs.org/@polkadot/extension-dapp/-/extension-dapp-0.46.6.tgz", + "integrity": "sha512-J8uhlAhCJkrVimS/ItCdXwgce9mbGZBwAAZLk9vxz7MGb9KkBV9wjy7xas7EPkA1fkSZ4hq+G9l8ybd+uwgp6Q==", + "dependencies": { + "@polkadot/extension-inject": "0.46.6", + "@polkadot/util": "^12.6.1", + "@polkadot/util-crypto": "^12.6.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/api": "*", + "@polkadot/util": "*", + "@polkadot/util-crypto": "*" + } + }, + "node_modules/@polkadot/extension-inject": { + "version": "0.46.6", + "resolved": "https://registry.npmjs.org/@polkadot/extension-inject/-/extension-inject-0.46.6.tgz", + "integrity": "sha512-5lJzL/iQ9oUcIDcER22Hxdjj4S9CoWS09yQoAKkfAmZMuTJkL/j36m7AnpNPN5ohWoPyd1Yl/JfwtoLmtRoZog==", + "dependencies": { + "@polkadot/api": "^10.11.1", + "@polkadot/rpc-provider": "^10.11.1", + "@polkadot/types": "^10.11.1", + "@polkadot/util": "^12.6.1", + "@polkadot/util-crypto": "^12.6.1", + "@polkadot/x-global": "^12.6.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/api": "*", + "@polkadot/util": "*" + } + }, "node_modules/@polkadot/keyring": { "version": "12.6.2", "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.6.2.tgz", @@ -2289,6 +2377,20 @@ "node": ">=18" } }, + "node_modules/@polkadot/phishing": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@polkadot/phishing/-/phishing-0.22.1.tgz", + "integrity": "sha512-IDII8jSdfoAHyABGlgTg6O8xpUYaSDc+NSnJ8U/V8XCjVYAEe3j/w/H3LnXKMVKWHexbpLzO4MMbu5ldNw7AjQ==", + "dependencies": { + "@polkadot/util": "^12.6.1", + "@polkadot/util-crypto": "^12.6.1", + "@polkadot/x-fetch": "^12.6.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@polkadot/rpc-augment": { "version": "10.11.2", "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.11.2.tgz", @@ -2983,83 +3085,83 @@ } }, "node_modules/@sentry-internal/feedback": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.99.0.tgz", - "integrity": "sha512-exIO1o+bE0MW4z30FxC0cYzJ4ZHSMlDPMHCBDPzU+MWGQc/fb8s58QUrx5Dnm6HTh9G3H+YlroCxIo9u0GSwGQ==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.100.1.tgz", + "integrity": "sha512-yqcRVnjf+qS+tC4NxOKLJOaSJ+csHmh/dHUzvCTkf5rLsplwXYRnny2r0tqGTQ4tuXMxwgSMKPYwicg81P+xuw==", "dependencies": { - "@sentry/core": "7.99.0", - "@sentry/types": "7.99.0", - "@sentry/utils": "7.99.0" + "@sentry/core": "7.100.1", + "@sentry/types": "7.100.1", + "@sentry/utils": "7.100.1" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.99.0.tgz", - "integrity": "sha512-PoIkfusToDq0snfl2M6HJx/1KJYtXxYhQplrn11kYadO04SdG0XGXf4h7wBTMEQ7LDEAtQyvsOu4nEQtTO3YjQ==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.100.1.tgz", + "integrity": "sha512-TnqxqJGhbFhhYRhTG2WLFer+lVieV7mNGeIxFBiw1L4kuj8KGl+C0sknssKyZSRVJFSahhHIosHJGRMkkD//7g==", "dependencies": { - "@sentry/core": "7.99.0", - "@sentry/replay": "7.99.0", - "@sentry/types": "7.99.0", - "@sentry/utils": "7.99.0" + "@sentry/core": "7.100.1", + "@sentry/replay": "7.100.1", + "@sentry/types": "7.100.1", + "@sentry/utils": "7.100.1" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.99.0.tgz", - "integrity": "sha512-z3JQhHjoM1KdM20qrHwRClKJrNLr2CcKtCluq7xevLtXHJWNAQQbafnWD+Aoj85EWXBzKt9yJMv2ltcXJ+at+w==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.100.1.tgz", + "integrity": "sha512-+u9RRf5eL3StiyiRyAHZmdkAR7GTSGx4Mt4Lmi5NEtCcWlTGZ1QgW2r8ZbhouVmTiJkjhQgYCyej3cojtazeJg==", "dependencies": { - "@sentry/core": "7.99.0", - "@sentry/types": "7.99.0", - "@sentry/utils": "7.99.0" + "@sentry/core": "7.100.1", + "@sentry/types": "7.100.1", + "@sentry/utils": "7.100.1" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/browser": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.99.0.tgz", - "integrity": "sha512-bgfoUv3wkwwLgN5YUOe0ibB3y268ZCnamZh6nLFqnY/UBKC1+FXWFdvzVON/XKUm62LF8wlpCybOf08ebNj2yg==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.100.1.tgz", + "integrity": "sha512-IxHQ08ixf0bmaWpe4yt1J4UUsOpg02fxax9z3tOQYXw5MSzz5pDXn8M8DFUVJB3wWuyXhHXTub9yD3VIP9fnoA==", "dependencies": { - "@sentry-internal/feedback": "7.99.0", - "@sentry-internal/replay-canvas": "7.99.0", - "@sentry-internal/tracing": "7.99.0", - "@sentry/core": "7.99.0", - "@sentry/replay": "7.99.0", - "@sentry/types": "7.99.0", - "@sentry/utils": "7.99.0" + "@sentry-internal/feedback": "7.100.1", + "@sentry-internal/replay-canvas": "7.100.1", + "@sentry-internal/tracing": "7.100.1", + "@sentry/core": "7.100.1", + "@sentry/replay": "7.100.1", + "@sentry/types": "7.100.1", + "@sentry/utils": "7.100.1" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/core": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz", - "integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.100.1.tgz", + "integrity": "sha512-f+ItUge/o9AjlveQq0ZUbQauKlPH1FIJbC1TRaYLJ4KNfOdrsh8yZ29RmWv0cFJ/e+FGTr603gWpRPObF5rM8Q==", "dependencies": { - "@sentry/types": "7.99.0", - "@sentry/utils": "7.99.0" + "@sentry/types": "7.100.1", + "@sentry/utils": "7.100.1" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/react": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.99.0.tgz", - "integrity": "sha512-RtHwgzMHJhzJfSQpVG0SDPQYMTGDX3Q37/YWI59S4ALMbSW4/F6n/eQAvGVYZKbh2UCSqgFuRWaXOYkSZT17wA==", - "dependencies": { - "@sentry/browser": "7.99.0", - "@sentry/core": "7.99.0", - "@sentry/types": "7.99.0", - "@sentry/utils": "7.99.0", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.100.1.tgz", + "integrity": "sha512-EdrBtrXVLK2LSx4Rvz/nQP7HZUZQmr+t3GHV8436RAhF6vs5mntACVMBoQJRWiUvtZ1iRo3rIsIdah7DLiFPgQ==", + "dependencies": { + "@sentry/browser": "7.100.1", + "@sentry/core": "7.100.1", + "@sentry/types": "7.100.1", + "@sentry/utils": "7.100.1", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -3070,33 +3172,33 @@ } }, "node_modules/@sentry/replay": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.99.0.tgz", - "integrity": "sha512-gyN/I2WpQrLAZDT+rScB/0jnFL2knEVBo8U8/OVt8gNP20Pq8T/rDZKO/TG0cBfvULDUbJj2P4CJryn2p/O2rA==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.100.1.tgz", + "integrity": "sha512-B1NFjzGEFaqejxBRdUyEzH8ChXc2kfiqlA/W/Lg0aoWIl2/7nuMk+l4ld9gW5F5bIAXDTVd5vYltb1lWEbpr7w==", "dependencies": { - "@sentry-internal/tracing": "7.99.0", - "@sentry/core": "7.99.0", - "@sentry/types": "7.99.0", - "@sentry/utils": "7.99.0" + "@sentry-internal/tracing": "7.100.1", + "@sentry/core": "7.100.1", + "@sentry/types": "7.100.1", + "@sentry/utils": "7.100.1" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/types": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz", - "integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.100.1.tgz", + "integrity": "sha512-fLM+LedHuKzOd8IhXBqaQuym+AA519MGjeczBa5kGakes/BbAsUMwsNfjsKQedp7Kh44RgYF99jwoRPK2oDrXw==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.99.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz", - "integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==", + "version": "7.100.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.100.1.tgz", + "integrity": "sha512-Ve6dXr1o6xiBe3VCoJgiutmBKrugryI65EZAbYto5XI+t+PjiLLf9wXtEMF24ZrwImo4Lv3E9Uqza+fWkEbw6A==", "dependencies": { - "@sentry/types": "7.99.0" + "@sentry/types": "7.100.1" }, "engines": { "node": ">=8" @@ -3206,9 +3308,9 @@ "dev": true }, "node_modules/@testing-library/jest-dom": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.1.tgz", - "integrity": "sha512-Z7qMM3J2Zw5H/nC2/5CYx5YcuaD56JmDFKNIozZ89VIo6o6Y9FMhssics4e2madEKYDNEpZz3+glPGz0yWMOag==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz", + "integrity": "sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -3449,9 +3551,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.52", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.52.tgz", - "integrity": "sha512-E/YjWh3tH+qsLKaUzgpZb5AY0ChVa+ZJzF7ogehVILrFpdQk6nC/WXOv0bfFEABbXbgNxLBGU7IIZByPKb6eBw==", + "version": "18.2.55", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz", + "integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -3480,17 +3582,23 @@ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", - "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/type-utils": "6.20.0", - "@typescript-eslint/utils": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3516,15 +3624,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", - "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -3544,13 +3652,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", - "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3561,13 +3669,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", - "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.20.0", - "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3588,9 +3696,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", - "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3601,13 +3709,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", - "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3629,17 +3737,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", - "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -3654,12 +3762,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", - "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4025,13 +4133,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4065,17 +4176,36 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4121,30 +4251,31 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", - "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -4353,14 +4484,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", + "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.3", + "set-function-length": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4385,9 +4520,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001583", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", - "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", + "version": "1.0.30001585", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", + "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", "dev": true, "funding": [ { @@ -4448,16 +4583,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4470,6 +4599,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -4762,14 +4894,15 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", + "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.2", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4898,9 +5031,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.656", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", - "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "version": "1.4.659", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.659.tgz", + "integrity": "sha512-sRJ3nV3HowrYpBtPF9bASQV7OW49IgZC01Xiq43WfSE3RTCkK0/JidoCmR73Hyc1mN+l/H4Yqx0eNiomvExFZg==", "dev": true }, "node_modules/elliptic": { @@ -4993,6 +5126,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -5114,9 +5262,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -5762,9 +5910,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", - "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -6022,16 +6170,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6049,13 +6201,13 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.1.tgz", + "integrity": "sha512-KmuibvwbWaM4BHcBRYwJfZ1JxyJeBwB8ct9YYu67SvYdbEIlcQ2e56dHxfbobqW38GXo8/zDFqJeGtHiVbWyQw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -6483,12 +6635,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -7257,9 +7409,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -7787,15 +7939,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.hasown": { @@ -8061,9 +8214,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.34", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz", + "integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==", "dev": true, "funding": [ { @@ -8449,9 +8602,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.50.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.50.0.tgz", - "integrity": "sha512-AOhuzM3RdP09ZCnq+Z0yvKGHK25yiOX5phwxjV9L7U6HMla10ezkBnvQ+Pk4GTuDfsC5P2zza3k8mawFwFLVuQ==", + "version": "7.50.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.50.1.tgz", + "integrity": "sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ==", "engines": { "node": ">=12.22.0" }, @@ -8464,11 +8617,11 @@ } }, "node_modules/react-i18next": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.0.1.tgz", - "integrity": "sha512-TMV8hFismBmpMdIehoFHin/okfvgjFhp723RYgIqB4XyhDobVMyukyM3Z8wtTRmajyFMZrBl/OaaXF2P6WjUAw==", + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.0.5.tgz", + "integrity": "sha512-5+bQSeEtgJrMBABBL5lO7jPdSNAbeAZ+MlFWDw//7FnVacuVu3l9EeWFzBQvZsKy+cihkbThWOAThEdH8YjGEw==", "dependencies": { - "@babel/runtime": "^7.22.5", + "@babel/runtime": "^7.23.9", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { @@ -8621,15 +8774,16 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", - "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", + "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -8858,13 +9012,13 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", - "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, "engines": { @@ -8906,9 +9060,9 @@ "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8936,14 +9090,15 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.1" }, @@ -8987,14 +9142,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9598,12 +9757,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -9727,14 +9886,14 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz", + "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -9805,9 +9964,9 @@ } }, "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", + "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", "dev": true }, "node_modules/unbox-primitive": { @@ -9900,6 +10059,18 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -9921,9 +10092,9 @@ } }, "node_modules/virtua": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/virtua/-/virtua-0.23.2.tgz", - "integrity": "sha512-LeLVubuO+Saxi3yRbr/cBYyCmM60v1IQLHNnoSzTowZQW7o9qoAY9JfTXjagseokUoo5PVQjovG5rSUcbCmOFw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/virtua/-/virtua-0.24.0.tgz", + "integrity": "sha512-TNtQHh6NjSQPgzpc3MDYC97VWAEpphMOgLN7F1m5RFQzzVnw2du/HKQGJEOzeby/elWX3MklROhqyofw3bI84g==", "peerDependencies": { "react": ">=16.14.0", "react-dom": ">=16.14.0", diff --git a/package.json b/package.json index 64eba5df..4a63314c 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kuma-wallet", "displayName": "Kuma Wallet", - "version": "0.4.3", + "version": "0.4.4", "description": "Kuma a cross-chain wallet that offers seamless management and transfer of assets between EVM and WASM chains.", "author": "Blockcoders Engineering ", "license": "MIT", @@ -38,10 +38,11 @@ "@metamask/browser-passworder": "^4.3.0", "@polkadot/api": "10.11.2", "@polkadot/api-contract": "10.11.2", + "@polkadot/extension-base": "^0.46.6", "@polkadot/ui-keyring": "3.6.4", "@polkadot/util": "12.6.2", "@polkadot/util-crypto": "12.6.2", - "@sentry/react": "^7.99.0", + "@sentry/react": "^7.100.1", "@types/lodash.debounce": "^4.0.9", "axios": "^1.6.7", "ethers": "^5.7.2", @@ -54,29 +55,31 @@ "react": "^18.2.0", "react-dnd-multi-backend": "^8.0.3", "react-dom": "^18.2.0", - "react-hook-form": "^7.50.0", - "react-i18next": "^14.0.1", + "react-hook-form": "^7.50.1", + "react-i18next": "^14.0.5", "react-icons": "^5.0.1", "react-number-format": "^5.3.1", "react-qr-code": "^2.0.12", "react-router-dom": "^6.22.0", "react-toastify": "^10.0.4", - "virtua": "^0.23.2", + "uuid": "^9.0.1", + "virtua": "^0.24.0", "yup": "^0.32.11" }, "devDependencies": { "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@polkadot/types-codec": "10.11.2", - "@testing-library/jest-dom": "^6.4.1", + "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", "@types/chrome": "^0.0.260", "@types/node": "^20.11.16", "@types/randomcolor": "^0.5.9", - "@types/react": "^18.2.52", + "@types/react": "^18.2.55", "@types/react-dom": "^18.2.18", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.2.1", "@vitest/coverage-v8": "^1.2.2", "autoprefixer": "^10.4.17", @@ -90,7 +93,7 @@ "fs-extra": "^11.2.0", "jsdom": "^24.0.0", "nodemon": "^3.0.3", - "postcss": "^8.4.33", + "postcss": "^8.4.34", "rollup-plugin-polyfill-node": "^0.13.0", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", diff --git a/public/images/blockcoders.svg b/public/images/blockcoders.svg new file mode 100644 index 00000000..4f1161ce --- /dev/null +++ b/public/images/blockcoders.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/Extension.ts b/src/Extension.ts deleted file mode 100644 index 4e415ff2..00000000 --- a/src/Extension.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { AccountKey, AccountType } from "./accounts/types"; -import Storage from "./storage/Storage"; -import AccountManager from "./accounts/AccountManager"; -import Setting from "./storage/entities/settings/Setting"; -import Network from "./storage/entities/Network"; -import Accounts from "./storage/entities/Accounts"; -import Account from "./storage/entities/Account"; -import Vault from "./storage/entities/Vault"; -import Auth from "./storage/Auth"; -import SelectedAccount from "./storage/entities/SelectedAccount"; -import Settings from "./storage/entities/settings/Settings"; -import { - SettingKey, - SettingType, - SettingValue, -} from "./storage/entities/settings/types"; -import Registry from "./storage/entities/registry/Registry"; -import Contact from "./storage/entities/registry/Contact"; -import Record from "./storage/entities/activity/Record"; -import Activity from "./storage/entities/activity/Activity"; -import Chains, { Chain } from "./storage/entities/Chains"; -import Register from "./storage/entities/registry/Register"; -import { RecordStatus } from "./storage/entities/activity/types"; -import Assets from "./storage/entities/Assets"; -import TrustedSites from "./storage/entities/TrustedSites"; -import { PASSWORD_REGEX, PRIVATE_KEY_OR_SEED_REGEX } from "./utils/constants"; -import { version } from "./utils/env"; -import Swap, { SwapData } from "./storage/entities/registry/Swap"; - -export default class Extension { - static get version() { - return version; - } - - private static validatePasswordFormat(password: string) { - if (!password) throw new Error("password_required"); - if (!PASSWORD_REGEX.test(password)) throw new Error("password_invalid"); - } - - private static validatePrivateKeyOrSeedFormat(privateKeyOrSeed: string) { - if (!privateKeyOrSeed) throw new Error("private_key_or_seed_required"); - if (!PRIVATE_KEY_OR_SEED_REGEX.test(privateKeyOrSeed)) - throw new Error("private_key_or_seed_invalid"); - } - - private static async signUp(password = "", privateKeyOrSeed: string) { - try { - Extension.validatePasswordFormat(password); - Extension.validatePrivateKeyOrSeedFormat(privateKeyOrSeed); - await Storage.init(password, privateKeyOrSeed); - } catch (error) { - Storage.getInstance().resetWallet(); - Auth.signOut(); - throw error; - } - } - - static isAuthorized(): boolean { - return Auth.isAuthorized(); - } - - static async createAccounts( - seed: string, - name: string, - password?: string, - isSignUp = true - ) { - if (isSignUp) { - await Extension.signUp(password, seed); - } - const wasmAccount = await AccountManager.addAccount( - AccountType.WASM, - seed, - name - ); - const evmAccount = await AccountManager.addAccount( - AccountType.EVM, - seed, - name - ); - - const selectedAccount = await Extension.getSelectedAccount(); - - if (isSignUp) { - await this.setSelectedAccount(wasmAccount); - } else { - await this.setSelectedAccount( - selectedAccount?.type.includes("EVM") ? evmAccount : wasmAccount - ); - } - - return true; - } - - static async importAccount( - name: string, - privateKeyOrSeed: string, - password: string | undefined, - type: AccountType.IMPORTED_EVM | AccountType.IMPORTED_WASM, - isSignUp = true - ) { - if (isSignUp) { - await Extension.signUp(password, privateKeyOrSeed); - } - const account = await AccountManager.importAccount( - name, - privateKeyOrSeed, - type - ); - this.setSelectedAccount(account); - } - - static async restorePassword(privateKeyOrSeed: string, newPassword: string) { - Extension.validatePasswordFormat(newPassword); - Extension.validatePrivateKeyOrSeedFormat(privateKeyOrSeed); - await AccountManager.restorePassword(privateKeyOrSeed, newPassword); - } - - static removeAccount(key: AccountKey) { - AccountManager.remove(key); - } - - static async changeAccountName(key: AccountKey, newName: string) { - const account = await AccountManager.changeName(key, newName); - await SelectedAccount.set(account); - } - - static async resetWallet() { - if (!Auth.isAuthorized()) { - throw new Error("not_authorized"); - } - await Storage.getInstance().resetWallet(); - } - - static async signIn(password: string) { - await Auth.signIn(password); - } - - static alreadySignedUp() { - return Vault.alreadySignedUp(); - } - - static async areAccountsInitialized(): Promise { - try { - const accounts = await Accounts.get(); - if (!accounts) return false; - return AccountManager.areAccountsInitialized(accounts); - } catch (error) { - return false; - } - } - - static async signOut() { - Auth.signOut(); - } - - static async isSessionActive() { - return Auth.isSessionActive(); - } - - static async showKey(): Promise { - const selectedAccount = await SelectedAccount.get(); - if (!selectedAccount || !selectedAccount?.value?.keyring) return undefined; - const { keyring: type } = selectedAccount.value; - - const address = selectedAccount.key.split("-")[1]; - - const keyring = await Vault.getKeyring(type); - return keyring.getKey(address); - } - - static async getAccount(key: AccountKey): Promise { - return AccountManager.getAccount(key); - } - - static async getAllAccounts( - type: AccountType[] | null = null - ): Promise { - const accounts = await AccountManager.getAll(type); - if (!accounts) return []; - return accounts.getAll(); - } - - static async deriveAccount( - name: string, - type: AccountType - ): Promise { - const account = await AccountManager.derive(name, type); - await this.setSelectedAccount(account); - return account; - } - - static async setNetwork(chain: Chain): Promise { - const network = Network.getInstance(); - network.set(chain); - await Network.set(network); - return true; - } - - static async setSelectedAccount(account: Account) { - await SelectedAccount.set( - SelectedAccount.fromAccount(account) - ); - } - - static async getSelectedAccount(): Promise { - return SelectedAccount.get(); - } - - static async getNetwork(): Promise { - return Network.get(); - } - - static async getGeneralSettings(): Promise { - const settings = await Settings.get(); - if (!settings) throw new Error("failed_to_get_settings"); - return settings.getAll(SettingType.GENERAL); - } - - static async getAdvancedSettings(): Promise { - const settings = await Settings.get(); - if (!settings) throw new Error("failed_to_get_settings"); - return settings.getAll(SettingType.ADVANCED); - } - - static async getSetting( - type: SettingType, - key: SettingKey - ): Promise { - const settings = await Settings.get(); - if (!settings) throw new Error("failed_to_get_settings"); - return settings.get(type, key); - } - - static async updateSetting( - type: SettingType, - key: SettingKey, - value: SettingValue - ) { - const settings = await Settings.get(); - if (!settings) throw new Error("failed_to_get_settings"); - settings.update(type, key, value); - await Settings.set(settings); - } - - static async getContacts(): Promise { - const registry = await Registry.get(); - if (!registry) throw new Error("failed_to_get_registry"); - return registry.getAllContacts(); - } - - static async getRegistryAddresses() { - const registry = await Registry.get(); - if (!registry) throw new Error("failed_to_get_registry"); - const { chain } = await Network.get(); - if (!chain) throw new Error("failed_to_get_network"); - const types = chain.supportedAccounts || []; - const accounts = await AccountManager.getAll(types); - if (!accounts) throw new Error("failed_to_get_accounts"); - return { - ownAccounts: accounts - .getAll() - .map( - (account) => new Contact(account.value.name, account.value.address) - ), - contacts: registry.getAllContacts(), - recent: registry.getRecentAddresses(chain.name), - }; - } - - static async saveContact(contact: Contact) { - await Registry.addContact(contact); - } - - static async removeContact(address: string) { - await Registry.removeContact(address); - } - - static async getActivity(): Promise { - return Activity.getRecords(); - } - - static async getAllChains(): Promise { - const newChains = await Chains.loadChains(); - const chains = await Chains.get(); - if (!chains) throw new Error("failed_to_get_chains"); - - if (newChains) { - const network = await Network.get(); - const selectedNetwork = network.chain; - const selectedNetworkInMainnets = newChains.mainnets.find( - (chain) => - chain.nativeCurrency.symbol === selectedNetwork?.nativeCurrency.symbol - ); - - const selectedNetworkInTestnets = newChains.testnets.find( - (chain) => - chain.nativeCurrency.symbol === selectedNetwork?.nativeCurrency.symbol - ); - - let chainToUpdate = null; - - if (selectedNetworkInMainnets) { - chainToUpdate = selectedNetworkInMainnets; - } - - if (selectedNetworkInTestnets) { - chainToUpdate = selectedNetworkInTestnets; - } - - if (chainToUpdate) { - network.chain = chainToUpdate; - await Network.set(network); - } - } - - return chains; - } - - static async saveCustomChain(chain: Chain) { - await Chains.saveCustomChain(chain); - } - - static async removeCustomChain(chainName: string) { - await Chains.removeCustomChain(chainName); - } - - static async getXCMChains(chainName: string): Promise { - const { xcm } = (await Chains.getByName(chainName)) || {}; - if (!xcm) throw new Error("failed_to_get_chain"); - const chains = await Chains.get(); - if (!chains) throw new Error("failed_to_get_chains"); - return chains.getAll().filter((chain) => xcm.includes(chain.name)); - } - - static async addActivity(txHash: string, record: Record) { - await Activity.addRecord(txHash, record); - const { address, network } = record; - const register = new Register(address, Date.now()); - await Registry.addRecentAddress(network, register); - } - - static async updateActivity( - txHash: string, - status: RecordStatus, - error?: string | undefined - ) { - await Activity.updateRecordStatus(txHash, status, error); - } - - static async addAsset( - chain: string, - asset: { symbol: string; address: string; decimals: number } - ) { - return Assets.addAsset(chain, asset); - } - - static async getAssetsByChain(chain: string) { - return Assets.getByChain(chain); - } - - static async getTrustedSites(): Promise { - return TrustedSites.getAll(); - } - - static async addTrustedSite(site: string) { - return TrustedSites.addSite(site); - } - - static async removeTrustedSite(site: string) { - return TrustedSites.removeSite(site); - } - - static async getSwapsByProtocol(protocol: string) { - return Swap.getSwapsByProtocol(protocol); - } - - static async addSwap(protocol: string, swap: SwapData) { - return Swap.addSwap(protocol, swap); - } -} diff --git a/src/components/common/PageWrapper.tsx b/src/components/common/PageWrapper.tsx index 222e00e9..877f4c48 100644 --- a/src/components/common/PageWrapper.tsx +++ b/src/components/common/PageWrapper.tsx @@ -1,5 +1,4 @@ -import { HTMLAttributes } from "react"; -import { FC, PropsWithChildren } from "react"; +import { FC, PropsWithChildren, HTMLAttributes } from "react"; interface PageWrapperProps extends PropsWithChildren { contentClassName?: HTMLAttributes["className"]; diff --git a/src/components/common/ReEnterPassword.tsx b/src/components/common/ReEnterPassword.tsx index 07ee77c9..d7ec2510 100644 --- a/src/components/common/ReEnterPassword.tsx +++ b/src/components/common/ReEnterPassword.tsx @@ -4,10 +4,10 @@ import { useTranslation } from "react-i18next"; import { Button } from "./Button"; import { useLoading, useToast } from "@src/hooks"; import { BsEye, BsEyeSlash } from "react-icons/bs"; -import Extension from "@src/Extension"; import { FiChevronLeft } from "react-icons/fi"; import { useNavigate } from "react-router-dom"; import { captureError } from "@src/utils/error-handling"; +import { messageAPI } from "@src/messageAPI/api"; export const ReEnterPassword = ({ cb }: { cb?: () => void }) => { const { t } = useTranslation("re_enter_password"); @@ -23,15 +23,18 @@ export const ReEnterPassword = ({ cb }: { cb?: () => void }) => { }; useEffect(() => { - (() => { - setShowDialog(!Extension.isAuthorized()); + (async () => { + const isAuth = await messageAPI.isAuthorized(); + setShowDialog(!isAuth) })(); }, []); const signIn = async () => { starLoading(); try { - await Extension?.signIn(password); + await messageAPI.signIn({ + password, + }); setShowDialog(false); await cb?.(); } catch (error) { diff --git a/src/components/wrapper/ValidationWrapper.tsx b/src/components/wrapper/ValidationWrapper.tsx index 9bd00f3e..fe4ddf32 100644 --- a/src/components/wrapper/ValidationWrapper.tsx +++ b/src/components/wrapper/ValidationWrapper.tsx @@ -1,4 +1,3 @@ -import Extension from "@src/Extension"; import { useLoading } from "@src/hooks"; import { SignIn } from "@src/pages"; import { useAccountContext, useNetworkContext } from "@src/providers"; @@ -12,6 +11,7 @@ import { import { ConfirmTrustedSite } from "../common"; import { parseIncomingQuery } from "@src/utils/utils"; import { getWebAPI } from "@src/utils/env"; +import { messageAPI } from "@src/messageAPI/api"; const WebAPI = getWebAPI(); @@ -39,7 +39,7 @@ export const ValidationWrapper: FC = ({ const { isLoading, starLoading, endLoading } = useLoading(true); const getTrustedSites = async () => { - const sites = await Extension.getTrustedSites(); + const sites = await messageAPI.getTrustedSites(); setTrustedSites(sites); }; @@ -48,9 +48,9 @@ export const ValidationWrapper: FC = ({ }; const saveTrustedSite = async () => { - const trustedSites = await Extension.getTrustedSites(); + const trustedSites = await messageAPI.getTrustedSites(); if (trustedSites && !trustedSites.includes(origin)) { - await Extension.addTrustedSite(origin); + await messageAPI.addTrustedSite({ site: origin }); setTrustedSites([...trustedSites, origin]); } }; @@ -70,7 +70,7 @@ export const ValidationWrapper: FC = ({ const load = async () => { starLoading(); - const isSessionActive = await Extension.isSessionActive(); + const isSessionActive = await messageAPI.isSessionActive(); setIsSessionActive(isSessionActive); await getTrustedSites(); diff --git a/src/constants/env.ts b/src/constants/env.ts new file mode 100644 index 00000000..9f3508c2 --- /dev/null +++ b/src/constants/env.ts @@ -0,0 +1,2 @@ +export const PORT_EXTENSION = "kuma-extension"; +export const PORT_CONTENT = "kuma-content"; diff --git a/src/Extension.test.ts b/src/entries/background/handlers/Extension.test.ts similarity index 57% rename from src/Extension.test.ts rename to src/entries/background/handlers/Extension.test.ts index e0712803..1157c5eb 100644 --- a/src/Extension.test.ts +++ b/src/entries/background/handlers/Extension.test.ts @@ -1,17 +1,18 @@ -import { AccountType } from "./accounts/types"; +import { AccountType } from "../../../accounts/types"; import Extension from "./Extension"; -import { selectedEVMChainMock } from "./tests/mocks/chain-mocks"; +import { selectedEVMChainMock } from "../../../tests/mocks/chain-mocks"; import { expect } from "vitest"; import { selectedEVMAccountMock, accountsMocks, -} from "./tests/mocks/account-mocks"; -import { SettingType } from "./storage/entities/settings/types"; -import { SettingKey } from "./storage/entities/settings/types"; -import { CHAINS } from "./constants/chains"; -import Record from "./storage/entities/activity/Record"; -import { RecordStatus } from "./storage/entities/activity/types"; -import { selectedWASMChainMock } from "./tests/mocks/chain-mocks"; +} from "../../../tests/mocks/account-mocks"; +import { SettingType } from "../../../storage/entities/settings/types"; +import { SettingKey } from "../../../storage/entities/settings/types"; +import { CHAINS } from "../../../constants/chains"; +import Record from "../../../storage/entities/activity/Record"; +import { RecordStatus } from "../../../storage/entities/activity/types"; +import { selectedWASMChainMock } from "../../../tests/mocks/chain-mocks"; + const accountManageMock = { saveBackup: vi.fn(), }; @@ -52,7 +53,7 @@ const contactsMock = [ describe("Extension", () => { beforeAll(() => { - vi.mock("./storage/Auth.ts", () => ({ + vi.mock("@src/storage/Auth", () => ({ default: { getInstance: vi.fn().mockReturnValue({ signUp: vi.fn(), @@ -63,10 +64,10 @@ describe("Extension", () => { isSessionActive: vi.fn().mockReturnValue(true), }, })); - vi.mock("./storage/entities/Account", () => ({ + vi.mock("@src/storage/entities/Account", () => ({ default: {}, })); - vi.mock("./storage/entities/Network", () => ({ + vi.mock("@src/storage/entities/Network", () => ({ default: { getInstance: vi.fn().mockReturnValue({ set: vi.fn(), @@ -74,7 +75,7 @@ describe("Extension", () => { set: vi.fn(), }, })); - vi.mock("./storage/entities/SelectedAccount.ts", () => { + vi.mock("@src/storage/entities/SelectedAccount", () => { class _SelectedAccount { static get() { return selectedEVMAccountMock; @@ -96,7 +97,7 @@ describe("Extension", () => { loadFromCache: vi.fn(), }, })); - vi.mock("./storage/entities/Vault.ts", () => ({ + vi.mock("@src/storage/entities/Vault", () => ({ default: { alreadySignedUp: vi.fn().mockReturnValue(true), }, @@ -107,13 +108,13 @@ describe("Extension", () => { vi.mock("./storage/entities/settings/Settings", () => ({ default: {}, })); - vi.mock("./storage/entities/registry/Registry", () => ({ + vi.mock("@src/storage/entities/registry/Registry", () => ({ default: {}, })); - vi.mock("./storage/entities/activity/Activity", () => ({ + vi.mock("@src/storage/entities/activity/Activity", () => ({ default: {}, })); - vi.mock("./storage/entities/Chains", () => ({ + vi.mock("@src/storage/entities/Chains", () => ({ default: {}, })); vi.mock("./storage/entities/BaseEntity", () => { @@ -123,7 +124,7 @@ describe("Extension", () => { default: BaseEntityMock, }; }); - vi.mock("./storage/Storage", () => ({ + vi.mock("@src/storage/Storage", () => ({ default: { getInstance: vi.fn().mockReturnValue({ init: vi.fn(), @@ -132,7 +133,7 @@ describe("Extension", () => { init: vi.fn(), }, })); - vi.mock("./accounts/AccountManager.ts", () => ({ + vi.mock("@src/accounts/AccountManager", () => ({ default: { saveBackup: () => accountManageMock.saveBackup(), addAccount: vi.fn().mockReturnValue({ @@ -161,13 +162,15 @@ describe("Extension", () => { describe("validatePasswordFormat", () => { it("should be valid", () => { - const result = Extension["validatePasswordFormat"]("Test.123"); + const extension = new Extension(); + const result = extension["validatePasswordFormat"]("Test.123"); expect(result).toBe(undefined); }); it("should return password_required error", () => { try { - Extension["validatePasswordFormat"](""); + const extension = new Extension(); + extension["validatePasswordFormat"](""); } catch (error) { expect(String(error)).toEqual("Error: password_required"); } @@ -175,7 +178,8 @@ describe("Extension", () => { it("should return password_invalid error", () => { try { - Extension["validatePasswordFormat"]("123456"); + const extension = new Extension(); + extension["validatePasswordFormat"]("123456"); } catch (error) { expect(String(error)).toEqual("Error: password_invalid"); } @@ -186,15 +190,17 @@ describe("Extension", () => { it("should be valid", () => { const SEED = "alarm skin dust shock fiber cruel virus brick slim culture hen leisure"; + const extension = new Extension(); - const result = Extension["validatePrivateKeyOrSeedFormat"](SEED); + const result = extension["validatePrivateKeyOrSeedFormat"](SEED); expect(result).toBe(undefined); }); it("should return private_key_or_seed_required error", () => { const SEED = ""; try { - Extension["validatePrivateKeyOrSeedFormat"](SEED); + const extension = new Extension(); + extension["validatePrivateKeyOrSeedFormat"](SEED); } catch (error) { expect(String(error)).toEqual("Error: private_key_or_seed_required"); } @@ -203,7 +209,8 @@ describe("Extension", () => { it("should return private_key_or_seed_invalid error", () => { const SEED = "123"; try { - Extension["validatePrivateKeyOrSeedFormat"](SEED); + const extension = new Extension(); + extension["validatePrivateKeyOrSeedFormat"](SEED); } catch (error) { expect(String(error)).toEqual("Error: private_key_or_seed_invalid"); } @@ -215,8 +222,12 @@ describe("Extension", () => { const PASSWORD = "Test.123"; const SEED = "alarm skin dust shock fiber cruel virus brick slim culture hen leisure"; + const extension = new Extension(); - const result = await Extension["signUp"](PASSWORD, SEED); + const result = await extension["signUp"]({ + password: PASSWORD, + privateKeyOrSeed: SEED, + }); expect(result).toBe(undefined); }); @@ -225,15 +236,20 @@ describe("Extension", () => { const SEED = ""; try { - await Extension["signUp"](PASSWORD, SEED); + const extension = new Extension(); + await extension["signUp"]({ + password: PASSWORD, + privateKeyOrSeed: SEED, + }); } catch (error) { expect(String(error)).toEqual("Error: private_key_or_seed_required"); } }); }); - it("should be authorized", () => { - const result = Extension.isAuthorized(); + it("should be authorized", async () => { + const extension = new Extension(); + const result = extension["isAuthorized"](); expect(result).toBe(true); }); @@ -245,12 +261,13 @@ describe("Extension", () => { const PASSWORD = "Test.123"; const IS_SIGNUP = true; - const result = await Extension.createAccounts( - SEED, - NAME, - PASSWORD, - IS_SIGNUP - ); + const extension = new Extension(); + const result = await extension["createAccounts"]({ + seed: SEED, + name: NAME, + password: PASSWORD, + isSignUp: IS_SIGNUP, + }); expect(result).toBe(true); }); @@ -261,12 +278,13 @@ describe("Extension", () => { const PASSWORD = "Test.123"; const IS_SIGNUP = false; - const result = await Extension.createAccounts( - SEED, - NAME, - PASSWORD, - IS_SIGNUP - ); + const extension = new Extension(); + const result = await extension["createAccounts"]({ + seed: SEED, + name: NAME, + password: PASSWORD, + isSignUp: IS_SIGNUP, + }); expect(result).toBe(true); }); }); @@ -279,14 +297,15 @@ describe("Extension", () => { const PASSWORD = "Test.123"; const type = AccountType.IMPORTED_WASM; const IS_SIGNUP = true; + const extension = new Extension(); - const result = await Extension.importAccount( - NAME, - SEED, - PASSWORD, + const result = await extension["importAccount"]({ + privateKeyOrSeed: SEED, + name: NAME, + password: PASSWORD, type, - IS_SIGNUP - ); + isSignUp: IS_SIGNUP, + }); expect(result).toBe(undefined); }); it("should import account with isSignUp in false", async () => { @@ -296,14 +315,15 @@ describe("Extension", () => { const PASSWORD = "Test.123"; const type = AccountType.IMPORTED_WASM; const IS_SIGNUP = false; + const extension = new Extension(); - const result = await Extension.importAccount( - NAME, - SEED, - PASSWORD, + const result = await extension["importAccount"]({ + privateKeyOrSeed: SEED, + name: NAME, + password: PASSWORD, type, - IS_SIGNUP - ); + isSignUp: IS_SIGNUP, + }); expect(result).toBe(undefined); }); }); @@ -312,36 +332,53 @@ describe("Extension", () => { const SEED = "alarm skin dust shock fiber cruel virus brick slim culture hen leisure"; const PASSWORD = "Test.123"; + const extension = new Extension(); - const result = await Extension.restorePassword(SEED, PASSWORD); + const result = await extension["restorePassword"]({ + privateKeyOrSeed: SEED, + newPassword: PASSWORD, + }); expect(result).toBe(undefined); }); it("should remove key", async () => { - const result = await Extension.removeAccount("EVM-1234"); + const extension = new Extension(); + + const result = await extension["removeAccount"]({ + key: "EVM-1234", + }); expect(result).toBe(undefined); }); it("should change name", async () => { - const result = await Extension.changeAccountName("EVM-1234", "test"); + const extension = new Extension(); + + const result = await extension["changeAccountName"]({ + key: "EVM-1234", + newName: "test", + }); expect(result).toBe(undefined); }); describe("resetWallet", () => { it("should reset wallet", async () => { - const Auth = (await import("./storage/Auth")).default; + const Auth = (await import("../../../storage/Auth")).default; Auth.isAuthorized = vi.fn().mockReturnValue(true); - const result = await Extension.resetWallet(); + const extension = new Extension(); + + const result = await extension["resetWallet"](); expect(result).toBe(undefined); }); it("should throw error", async () => { - const Auth = (await import("./storage/Auth")).default; + const Auth = (await import("../../../storage/Auth")).default; Auth.isAuthorized = vi.fn().mockReturnValue(false); try { - await Extension.resetWallet(); + const extension = new Extension(); + + await extension["resetWallet"](); } catch (error) { expect(String(error)).toEqual("Error: not_authorized"); } @@ -349,18 +386,22 @@ describe("Extension", () => { }); it("signIn", async () => { - const result = await Extension.signIn("Test.123"); + const extension = new Extension(); + const result = await extension["signIn"]({ + password: "Test.123", + }); expect(result).toBe(undefined); }); it("should be signed up", async () => { - const result = await Extension.alreadySignedUp(); + const extension = new Extension(); + const result = await extension["alreadySignedUp"](); expect(result).toBe(true); }); describe("areAccountsInitialized", () => { it("should true", async () => { - const Accounts = await import("./storage/entities/Accounts"); + const Accounts = await import("../../../storage/entities/Accounts"); Accounts.default.get = vi.fn().mockReturnValue({ data: { "EVM-1234": { @@ -369,24 +410,27 @@ describe("Extension", () => { }, }); - const result = await Extension.areAccountsInitialized(); + const extension = new Extension(); + const result = await extension["areAccountsInitialized"](); expect(result).toBe(true); }); it("should return false", async () => { - const Accounts = await import("./storage/entities/Accounts"); + const Accounts = await import("../../../storage/entities/Accounts"); Accounts.default.get = vi.fn().mockReturnValue(undefined); - const result = await Extension.areAccountsInitialized(); + const extension = new Extension(); + const result = await extension["areAccountsInitialized"](); expect(result).toBe(false); }); it("should return with catch error false", async () => { - const Accounts = await import("./storage/entities/Accounts"); + const Accounts = await import("../../../storage/entities/Accounts"); Accounts.default.get = vi.fn().mockRejectedValue(undefined); try { - await Extension.areAccountsInitialized(); + const extension = new Extension(); + await extension["areAccountsInitialized"](); } catch (error) { expect(error).toBe(false); } @@ -394,19 +438,21 @@ describe("Extension", () => { }); it("should sign out", async () => { - const result = await Extension.signOut(); + const extension = new Extension(); + const result = await extension["signOut"](); expect(result).toBe(undefined); }); it("should session be active", async () => { - const result = await Extension.isSessionActive(); + const extension = new Extension(); + const result = await extension["isSessionActive"](); expect(result).toBe(true); }); describe("showKey", () => { it("should return key", async () => { const SelectedAccount = ( - await import("./storage/entities/SelectedAccount") + await import("../../../storage/entities/SelectedAccount") ).default; SelectedAccount.get = vi.fn().mockReturnValue({ value: { @@ -417,60 +463,70 @@ describe("Extension", () => { key: "EVM-1234", }); - const Vault = (await import("./storage/entities/Vault")).default; + const Vault = (await import("../../../storage/entities/Vault")).default; Vault.getKeyring = vi.fn().mockReturnValue({ getKey: () => { return "1234"; }, }); - const result = await Extension.showKey(); + const extension = new Extension(); + const result = await extension["showKey"](); expect(result).toBe("1234"); }); it("should return undefined", async () => { const SelectedAccount = ( - await import("./storage/entities/SelectedAccount") + await import("../../../storage/entities/SelectedAccount") ).default; SelectedAccount.get = vi.fn().mockReturnValue(undefined); - const result = await Extension.showKey(); + const extension = new Extension(); + const result = await extension["showKey"](); expect(result).toBe(undefined); }); }); it("get account", async () => { - const result = await Extension.getAccount("EVM-1234"); + const extension = new Extension(); + const result = await extension["getAccount"]({ + key: "EVM-1234", + }); expect(result).toBe(undefined); }); describe("getAllAccounts", () => { it("should return all accounts", async () => { - const _AccountManager = await import("./accounts/AccountManager"); + const _AccountManager = await import("../../../accounts/AccountManager"); _AccountManager.default.getAll = vi.fn().mockReturnValue({ getAll: vi.fn().mockReturnValue(accountsMocks), }); - const result = await Extension.getAllAccounts(); - + const extension = new Extension(); + const result = await extension["getAllAccounts"]({ + type: null, + }); expect(result).toEqual(accountsMocks); }); it("should return empty array", async () => { - const _AccountManager = await import("./accounts/AccountManager"); + const _AccountManager = await import("../../../accounts/AccountManager"); _AccountManager.default.getAll = vi.fn().mockReturnValue(undefined); - const result = await Extension.getAllAccounts(); - + const extension = new Extension(); + const result = await extension["getAllAccounts"]({ + type: null, + }); expect(result).toEqual([]); }); }); it("should derive account", async () => { - const result = await Extension.deriveAccount( - "derived-evm", - AccountType.EVM - ); + const extension = new Extension(); + const result = await extension["deriveAccount"]({ + name: "derived-evm", + type: AccountType.EVM, + }); expect(result).toMatchObject({ key: "WASM-123", value: "0x123", @@ -479,59 +535,69 @@ describe("Extension", () => { }); it("should set network", async () => { - const result = await Extension.setNetwork(selectedWASMChainMock); + const extension = new Extension(); + const result = await extension["setNetwork"]({ + chain: selectedWASMChainMock, + }); expect(result).toBe(true); }); it("should return selected account", async () => { const _SelectedAccountMock = await import( - "./storage/entities/SelectedAccount" + "../../../storage/entities/SelectedAccount" ); _SelectedAccountMock.default.get = vi .fn() .mockReturnValue(selectedEVMAccountMock); - const result = await Extension.getSelectedAccount(); + const extension = new Extension(); + const result = await extension["getSelectedAccount"](); expect(result).toMatchObject(selectedEVMAccountMock); }); it("should return selected network", async () => { - const _NetowrkMock = await import("./storage/entities/Network"); + const _NetowrkMock = await import("../../../storage/entities/Network"); _NetowrkMock.default.get = vi.fn().mockReturnValue(selectedEVMChainMock); - const result = await Extension.getNetwork(); + const extension = new Extension(); + const result = await extension["getNetwork"](); expect(result).toMatchObject(selectedEVMChainMock); }); it("should get network", async () => { - const _Network = (await import("./storage/entities/Network")).default; + const _Network = (await import("../../../storage/entities/Network")) + .default; const get = vi.fn(); _Network.get = get; - Extension.getNetwork(); - + const extension = new Extension(); + extension["getNetwork"](); expect(get).toHaveBeenCalled(); }); describe("getGeneralSettings", () => { it("should return general settings", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const get = vi.fn().mockReturnValue(getSettingsMock); _Settings.get = get; - const result = await Extension.getGeneralSettings(); + const extension = new Extension(); + const result = await extension["getGeneralSettings"](); expect(result).toMatchObject(getSettingsMock.getAll()); }); it("should return error", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const get = vi.fn().mockReturnValue(undefined); _Settings.get = get; try { - await Extension.getGeneralSettings(); + const extension = new Extension(); + await extension["getGeneralSettings"](); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_settings"); @@ -541,23 +607,27 @@ describe("Extension", () => { describe("getAdvancedSettings", () => { it("should return general settings", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const get = vi.fn().mockReturnValue(getSettingsMock); _Settings.get = get; - const result = await Extension.getAdvancedSettings(); + const extension = new Extension(); + const result = await extension["getAdvancedSettings"](); expect(result).toMatchObject(getSettingsMock.getAll()); }); it("should return error", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const get = vi.fn().mockReturnValue(undefined); _Settings.get = get; try { - await Extension.getAdvancedSettings(); + const extension = new Extension(); + await extension["getAdvancedSettings"](); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_settings"); @@ -567,29 +637,33 @@ describe("Extension", () => { describe("getSetting", () => { it("should return general settings", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const get = vi.fn().mockReturnValue(getSettingsMock); _Settings.get = get; - const result = await Extension.getSetting( - SettingType.GENERAL, - SettingKey["LANGUAGES"] - ); + const extension = new Extension(); + const result = await extension["getSetting"]({ + type: SettingType.GENERAL, + key: SettingKey["LANGUAGES"], + }); expect(result).toMatchObject(getSettingsMock.get()); }); it("should return error", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const get = vi.fn().mockReturnValue(undefined); _Settings.get = get; try { - await Extension.getSetting( - SettingType.GENERAL, - SettingKey["LANGUAGES"] - ); + const extension = new Extension(); + await extension["getSetting"]({ + type: SettingType.GENERAL, + key: SettingKey["LANGUAGES"], + }); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_settings"); @@ -599,8 +673,9 @@ describe("Extension", () => { describe("updateSetting", () => { it("should return settings", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const set = vi.fn(); const update = vi.fn(); @@ -611,27 +686,30 @@ describe("Extension", () => { _Settings.get = get; _Settings.set = set; - await Extension.updateSetting( - SettingType.GENERAL, - SettingKey["LANGUAGES"], - "en" - ); + const extension = new Extension(); + await extension["updateSetting"]({ + type: SettingType.GENERAL, + key: SettingKey["LANGUAGES"], + value: "en", + }); expect(set).toHaveBeenCalled(); }); it("should return error", async () => { - const _Settings = (await import("./storage/entities/settings/Settings")) - .default; + const _Settings = ( + await import("../../../storage/entities/settings/Settings") + ).default; const get = vi.fn().mockReturnValue(undefined); _Settings.get = get; try { - await Extension.updateSetting( - SettingType.GENERAL, - SettingKey["LANGUAGES"], - "en" - ); + const extension = new Extension(); + await extension["updateSetting"]({ + type: SettingType.GENERAL, + key: SettingKey["LANGUAGES"], + value: "en", + }); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_settings"); @@ -641,26 +719,30 @@ describe("Extension", () => { describe("getContacts", () => { it("should return contacts", async () => { - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const get = vi.fn().mockReturnValue({ getAllContacts: vi.fn().mockReturnValue(contactsMock), }); _Registry.get = get; - const result = await Extension.getContacts(); + const extension = new Extension(); + const result = await extension["getContacts"](); expect(result).toMatchObject(contactsMock); }); it("should return error", async () => { - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const get = vi.fn().mockReturnValue(undefined); _Registry.get = get; try { - await Extension.getContacts(); + const extension = new Extension(); + await extension["getContacts"](); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_registry"); @@ -679,15 +761,17 @@ describe("Extension", () => { }, ]; - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const get = vi.fn().mockReturnValue({ getAllContacts: () => contactsMock, getRecentAddresses: () => [], }); _Registry.get = get; - const _Network = (await import("./storage/entities/Network")).default; + const _Network = (await import("../../../storage/entities/Network")) + .default; const getNetwork = vi.fn().mockReturnValue({ chain: { supportedAccounts: [], @@ -695,7 +779,7 @@ describe("Extension", () => { }); _Network.get = getNetwork; - const _AccountManage = (await import("./accounts/AccountManager")) + const _AccountManage = (await import("../../../accounts/AccountManager")) .default; const getAll = vi.fn().mockReturnValue({ getAll: () => { @@ -704,20 +788,22 @@ describe("Extension", () => { }); _AccountManage.getAll = getAll; - - const result = await Extension.getRegistryAddresses(); + const extension = new Extension(); + const result = await extension["getRegistryAddresses"](); expect(result.contacts).toMatchObject(contactsMock); }); it("should return registry error", async () => { - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const get = vi.fn().mockReturnValue(undefined); _Registry.get = get; try { - await Extension.getRegistryAddresses(); + const extension = new Extension(); + await extension["getRegistryAddresses"](); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_registry"); @@ -725,17 +811,20 @@ describe("Extension", () => { }); it("should return network error", async () => { - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const get = vi.fn().mockReturnValue({}); _Registry.get = get; - const _Network = (await import("./storage/entities/Network")).default; + const _Network = (await import("../../../storage/entities/Network")) + .default; const getNetwork = vi.fn().mockReturnValue({ chain: undefined }); _Network.get = getNetwork; try { - await Extension.getRegistryAddresses(); + const extension = new Extension(); + await extension["getRegistryAddresses"](); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_network"); @@ -743,12 +832,14 @@ describe("Extension", () => { }); it("should return network error", async () => { - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const get = vi.fn().mockReturnValue({}); _Registry.get = get; - const _Network = (await import("./storage/entities/Network")).default; + const _Network = (await import("../../../storage/entities/Network")) + .default; const getNetwork = vi.fn().mockReturnValue({ chain: { supportedAccounts: [], @@ -756,14 +847,15 @@ describe("Extension", () => { }); _Network.get = getNetwork; - const _AccountManage = (await import("./accounts/AccountManager")) + const _AccountManage = (await import("../../../accounts/AccountManager")) .default; const getAll = vi.fn().mockReturnValue(undefined); _AccountManage.getAll = getAll; try { - await Extension.getRegistryAddresses(); + const extension = new Extension(); + await extension["getRegistryAddresses"](); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_accounts"); @@ -773,61 +865,74 @@ describe("Extension", () => { describe("save contact", () => { it("should save contact", async () => { - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const addContact = vi.fn(); _Registry.addContact = addContact; - await Extension.saveContact({ - name: "account1", - address: "0x12345", + const extension = new Extension(); + + await extension["saveContact"]({ + contact: { + name: "account1", + address: "0x12345", + }, }); expect(addContact).toHaveBeenCalled(); }); }); it("removeContact", async () => { - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const removeContact = vi.fn(); _Registry.removeContact = removeContact; - await Extension.removeContact("0x12345"); + const extension = new Extension(); + await extension["removeContact"]({ address: "0x12345" }); expect(removeContact).toHaveBeenCalled(); }); it("getActivity", async () => { - const _Activity = (await import("./storage/entities/activity/Activity")) - .default; + const _Activity = ( + await import("../../../storage/entities/activity/Activity") + ).default; const getRecords = vi.fn(); _Activity.getRecords = getRecords; - await Extension.getActivity(); + const extension = new Extension(); + await extension["getActivity"](); expect(getRecords).toHaveBeenCalled(); }); describe("getAllChains", () => { it("should return all chains", async () => { - const _Chains = (await import("./storage/entities/Chains")).default; + const _Chains = (await import("../../../storage/entities/Chains")) + .default; const get = vi.fn().mockReturnValue(CHAINS); _Chains.get = get; _Chains.loadChains = vi.fn().mockReturnValue(undefined); - const result = await Extension.getAllChains(); + const extension = new Extension(); + const result = await extension["getAllChains"](); expect(result).toMatchObject(CHAINS); }); it("should return error", async () => { - const _Chains = (await import("./storage/entities/Chains")).default; + const _Chains = (await import("../../../storage/entities/Chains")) + .default; const get = vi.fn().mockReturnValue(undefined); _Chains.get = get; try { - await Extension.getAllChains(); + const extension = new Extension(); + await extension["getAllChains"](); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_chains"); @@ -836,28 +941,31 @@ describe("Extension", () => { }); it("saveCustomChain", async () => { - const _Chains = (await import("./storage/entities/Chains")).default; + const _Chains = (await import("../../../storage/entities/Chains")).default; const saveCustomChain = vi.fn(); _Chains.saveCustomChain = saveCustomChain; - Extension.saveCustomChain(CHAINS[0].chains[0]); + const extension = new Extension(); + extension["saveCustomChain"]({ chain: CHAINS[0].chains[0] }); expect(saveCustomChain).toHaveBeenCalled(); }); it("removeCustomChain", async () => { - const _Chains = (await import("./storage/entities/Chains")).default; + const _Chains = (await import("../../../storage/entities/Chains")).default; const removeCustomChain = vi.fn(); _Chains.removeCustomChain = removeCustomChain; - Extension.removeCustomChain(CHAINS[0].chains[0].name); + const extension = new Extension(); + extension["removeCustomChain"]({ chainName: CHAINS[0].chains[0].name }); expect(removeCustomChain).toHaveBeenCalled(); }); describe("getXCMChains", () => { it("should return xcm chains", async () => { - const _Chains = (await import("./storage/entities/Chains")).default; + const _Chains = (await import("../../../storage/entities/Chains")) + .default; const getByName = vi.fn().mockReturnValue({ xcm: ["moonbeam"] }); const get = vi.fn().mockReturnValue({ @@ -868,18 +976,25 @@ describe("Extension", () => { _Chains.getByName = getByName; _Chains.get = get; - const result = await Extension.getXCMChains(CHAINS[0].chains[0].name); + const extension = new Extension(); + const result = await extension["getXCMChains"]({ + chainName: CHAINS[0].chains[0].name, + }); expect(result).toMatchObject(CHAINS[0].chains); }); it("should return chain error", async () => { - const _Chains = (await import("./storage/entities/Chains")).default; + const _Chains = (await import("../../../storage/entities/Chains")) + .default; const getByName = vi.fn().mockReturnValue({ xcm: undefined }); _Chains.getByName = getByName; try { - await Extension.getXCMChains(CHAINS[0].chains[0].name); + const extension = new Extension(); + await extension["getXCMChains"]({ + chainName: CHAINS[0].chains[0].name, + }); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_chain"); @@ -887,7 +1002,8 @@ describe("Extension", () => { }); it("should return chains error", async () => { - const _Chains = (await import("./storage/entities/Chains")).default; + const _Chains = (await import("../../../storage/entities/Chains")) + .default; const getByName = vi.fn().mockReturnValue({ xcm: [] }); const get = vi.fn().mockReturnValue(undefined); @@ -895,7 +1011,10 @@ describe("Extension", () => { _Chains.get = get; try { - await Extension.getXCMChains(CHAINS[0].chains[0].name); + const extension = new Extension(); + await extension["getXCMChains"]({ + chainName: CHAINS[0].chains[0].name, + }); throw new Error("bad test"); } catch (error) { expect(String(error)).toEqual("Error: failed_to_get_chains"); @@ -904,78 +1023,98 @@ describe("Extension", () => { }); it("addActivity", async () => { - const _Activity = (await import("./storage/entities/activity/Activity")) - .default; + const _Activity = ( + await import("../../../storage/entities/activity/Activity") + ).default; const addRecord = vi.fn(); _Activity.addRecord = addRecord; - const _Registry = (await import("./storage/entities/registry/Registry")) - .default; + const _Registry = ( + await import("../../../storage/entities/registry/Registry") + ).default; const addRecent = vi.fn(); _Registry.addRecentAddress = addRecent; - await Extension.addActivity("0x1234", {} as Record); + const extension = new Extension(); + await extension["addActivity"]({ txHash: "0x1234", record: {} as Record }); expect(addRecent).toHaveBeenCalled(); }); it("updateActivity", async () => { - const _Activity = (await import("./storage/entities/activity/Activity")) - .default; + const _Activity = ( + await import("../../../storage/entities/activity/Activity") + ).default; const updateRecordStatus = vi.fn(); _Activity.updateRecordStatus = updateRecordStatus; - await Extension.updateActivity("0x1234", RecordStatus.SUCCESS); + const extension = new Extension(); + await extension["updateActivity"]({ + txHash: "0x1234", + status: RecordStatus.SUCCESS, + }); expect(updateRecordStatus).toHaveBeenCalled(); }); it("addAsset", async () => { - const _Assets = (await import("./storage/entities/Assets")).default; + const _Assets = (await import("../../../storage/entities/Assets")).default; const addAsset = vi.fn(); _Assets.addAsset = addAsset; - await Extension.addAsset( - CHAINS[0].chains[0].name, - {} as unknown as { symbol: string; address: string; decimals: number } - ); + const extension = new Extension(); + await extension["addAsset"]({ + chain: CHAINS[0].chains[0].name, + asset: {} as unknown as { + symbol: string; + address: string; + decimals: number; + }, + }); expect(addAsset).toHaveBeenCalled(); }); it("getAssetsByChain", async () => { - const _Assets = (await import("./storage/entities/Assets")).default; + const _Assets = (await import("../../../storage/entities/Assets")).default; const getByChain = vi.fn(); _Assets.getByChain = getByChain; - await Extension.getAssetsByChain(CHAINS[0].chains[0].name); + const extension = new Extension(); + await extension["getAssetsByChain"]({ chain: CHAINS[0].chains[0].name }); expect(getByChain).toHaveBeenCalled(); }); it("getTrustedSites", async () => { - const _TrustedSites = (await import("./storage/entities/TrustedSites")) - .default; + const _TrustedSites = ( + await import("../../../storage/entities/TrustedSites") + ).default; const getAll = vi.fn(); _TrustedSites.getAll = getAll; - Extension.getTrustedSites(); + const extension = new Extension(); + extension["getTrustedSites"](); expect(getAll).toHaveBeenCalled(); }); it("addTrustedSite", async () => { - const _TrustedSites = (await import("./storage/entities/TrustedSites")) - .default; + const _TrustedSites = ( + await import("../../../storage/entities/TrustedSites") + ).default; const addSite = vi.fn(); _TrustedSites.addSite = addSite; - Extension.addTrustedSite("https://test.com"); + const extension = new Extension(); + extension["addTrustedSite"]({ site: "https://test.com" }); expect(addSite).toHaveBeenCalled(); }); it("removeTrustedSite", async () => { - const _TrustedSites = (await import("./storage/entities/TrustedSites")) - .default; + const _TrustedSites = ( + await import("../../../storage/entities/TrustedSites") + ).default; const removeSite = vi.fn(); _TrustedSites.removeSite = removeSite; - Extension.removeTrustedSite("https://test.com"); + const extension = new Extension(); + extension["removeTrustedSite"]({ site: "https://test.com" }); expect(removeSite).toHaveBeenCalled(); }); }); diff --git a/src/entries/background/handlers/Extension.ts b/src/entries/background/handlers/Extension.ts new file mode 100644 index 00000000..f1159ff3 --- /dev/null +++ b/src/entries/background/handlers/Extension.ts @@ -0,0 +1,761 @@ +import { + PASSWORD_REGEX, + PRIVATE_KEY_OR_SEED_REGEX, +} from "@src/utils/constants"; + +import Storage from "@src/storage/Storage"; +import AccountManager from "@src/accounts/AccountManager"; +import Setting from "@src/storage/entities/settings/Setting"; +import Network from "@src/storage/entities/Network"; +import Accounts from "@src/storage/entities/Accounts"; +import Account from "@src/storage/entities/Account"; +import Vault from "@src/storage/entities/Vault"; +import Auth from "@src/storage/Auth"; +import SelectedAccount from "@src/storage/entities/SelectedAccount"; +import Settings from "@src/storage/entities/settings/Settings"; +import { SettingType } from "@src/storage/entities/settings/types"; +import Registry from "@src/storage/entities/registry/Registry"; +import Contact from "@src/storage/entities/registry/Contact"; +import Record from "@src/storage/entities/activity/Record"; +import Activity from "@src/storage/entities/activity/Activity"; +import Chains, { Chain } from "@src/storage/entities/Chains"; +import Register from "@src/storage/entities/registry/Register"; +import Assets from "@src/storage/entities/Assets"; +import TrustedSites from "@src/storage/entities/TrustedSites"; +import Swap from "@src/storage/entities/registry/Swap"; +import { AccountType } from "@src/accounts/types"; +import { version } from "@src/utils/env"; +import { + MessageTypes, + RequestAddActivity, + RequestAddAsset, + RequestAddSwap, + RequestAddTrustedSite, + RequestChangeAccountName, + RequestCreateAccount, + RequestDeriveAccount, + RequestGetAccount, + RequestGetAllAccounts, + RequestGetAssetsByChain, + RequestGetSetting, + RequestGetXCMChains, + RequestImportAccount, + RequestRemoveAccout, + RequestRemoveContact, + RequestRemoveCustomChain, + RequestRemoveTrustedSite, + RequestRestorePassword, + RequestSaveContact, + RequestSaveCustomChain, + RequestSendEvmTx, + RequestSendSubstrateTx, + RequestSetNetwork, + RequestSignIn, + RequestSignUp, + RequestSwapProtocol, + RequestTypes, + RequestUpdateActivity, + RequestUpdateSetting, + ResponseType, +} from "./request-types"; +import { ethers } from "ethers"; +import { ApiPromise, WsProvider } from "@polkadot/api"; +// import { Port } from "./types"; +import PolkadotKeyring from "@polkadot/ui-keyring"; +import { RecordStatus, RecordType } from "@src/storage/entities/activity/types"; +import { BN } from "@polkadot/util"; +import notificationIcon from "/icon-128.png"; + +export const getProvider = (rpc: string, type: string) => { + if (type.toLowerCase() === "evm") + return new ethers.providers.JsonRpcProvider(rpc as string); + + if (type.toLowerCase() === "wasm") + return ApiPromise.create({ provider: new WsProvider(rpc as string) }); +}; + +const getWebAPI = (): typeof chrome => { + return navigator.userAgent.match(/chrome|chromium|crios/i) + ? chrome + : window.browser; +}; + +const WebAPI = getWebAPI(); + +export default class Extension { + get version() { + return version; + } + + private validatePasswordFormat(password: string) { + if (!password) throw new Error("password_required"); + if (!PASSWORD_REGEX.test(password)) throw new Error("password_invalid"); + } + + private validatePrivateKeyOrSeedFormat(privateKeyOrSeed: string) { + if (!privateKeyOrSeed) throw new Error("private_key_or_seed_required"); + if (!PRIVATE_KEY_OR_SEED_REGEX.test(privateKeyOrSeed)) + throw new Error("private_key_or_seed_invalid"); + } + + private isAuthorized(): boolean { + return Auth.isAuthorized(); + } + + private async signUp({ password, privateKeyOrSeed }: RequestSignUp) { + try { + this.validatePasswordFormat(password); + this.validatePrivateKeyOrSeedFormat(privateKeyOrSeed); + await Storage.init(password, privateKeyOrSeed); + } catch (error) { + Storage.getInstance().resetWallet(); + Auth.signOut(); + throw error; + } + } + + private async createAccounts({ + seed, + name, + password, + isSignUp, + }: RequestCreateAccount) { + if (isSignUp) { + await this.signUp({ + password: password as string, + privateKeyOrSeed: seed, + }); + } + const wasmAccount = await AccountManager.addAccount( + AccountType.WASM, + seed, + name + ); + const evmAccount = await AccountManager.addAccount( + AccountType.EVM, + seed, + name + ); + + const selectedAccount = await this.getSelectedAccount(); + + if (isSignUp) { + await this.setSelectedAccount(wasmAccount); + } else { + await this.setSelectedAccount( + selectedAccount?.type.includes("EVM") ? evmAccount : wasmAccount + ); + } + + return true; + } + + private async importAccount({ + name, + privateKeyOrSeed, + password = "", + type, + isSignUp = true, + }: RequestImportAccount) { + if (isSignUp) { + await this.signUp({ password, privateKeyOrSeed }); + } + const account = await AccountManager.importAccount( + name, + privateKeyOrSeed, + type + ); + this.setSelectedAccount(account); + } + + private async restorePassword({ + privateKeyOrSeed, + newPassword, + }: RequestRestorePassword) { + this.validatePasswordFormat(newPassword); + this.validatePrivateKeyOrSeedFormat(privateKeyOrSeed); + await AccountManager.restorePassword(privateKeyOrSeed, newPassword); + } + + private removeAccount({ key }: RequestRemoveAccout) { + AccountManager.remove(key); + } + + private async changeAccountName({ key, newName }: RequestChangeAccountName) { + const account = await AccountManager.changeName(key, newName); + await SelectedAccount.set(account); + } + + private async resetWallet() { + if (!Auth.isAuthorized()) { + throw new Error("not_authorized"); + } + await Storage.getInstance().resetWallet(); + } + + private async signIn({ password }: RequestSignIn) { + await Auth.signIn(password); + } + + private alreadySignedUp() { + return Vault.alreadySignedUp(); + } + + private async areAccountsInitialized(): Promise { + try { + const accounts = await Accounts.get(); + if (!accounts) return false; + return AccountManager.areAccountsInitialized(accounts); + } catch (error) { + return false; + } + } + + private async signOut() { + Auth.signOut(); + } + + private async isSessionActive() { + return Auth.isSessionActive(); + } + + private async showKey(): Promise { + const selectedAccount = await SelectedAccount.get(); + if (!selectedAccount || !selectedAccount?.value?.keyring) return undefined; + const { keyring: type } = selectedAccount.value; + + const address = selectedAccount.key.split("-")[1]; + + const keyring = await Vault.getKeyring(type); + return keyring.getKey(address); + } + + private async getAccount({ + key, + }: RequestGetAccount): Promise { + return AccountManager.getAccount(key); + } + + private async getAllAccounts({ + type = null, + }: RequestGetAllAccounts): Promise { + const accounts = await AccountManager.getAll(type); + if (!accounts) return []; + return accounts.getAll(); + } + + private async deriveAccount({ + name, + type, + }: RequestDeriveAccount): Promise { + const account = await AccountManager.derive(name, type); + await this.setSelectedAccount(account); + return account; + } + + private async setNetwork({ chain }: RequestSetNetwork): Promise { + const network = Network.getInstance(); + network.set(chain); + await Network.set(network); + return true; + } + + private async setSelectedAccount(account: Account) { + await SelectedAccount.set( + SelectedAccount.fromAccount(account) + ); + } + + private async getSelectedAccount(): Promise { + return SelectedAccount.get(); + } + + private async getNetwork(): Promise { + return Network.get(); + } + + private async getGeneralSettings(): Promise { + const settings = await Settings.get(); + if (!settings) throw new Error("failed_to_get_settings"); + return settings.getAll(SettingType.GENERAL); + } + + private async getAdvancedSettings(): Promise { + const settings = await Settings.get(); + if (!settings) throw new Error("failed_to_get_settings"); + return settings.getAll(SettingType.ADVANCED); + } + + private async getSetting({ + type, + key, + }: RequestGetSetting): Promise { + const settings = await Settings.get(); + if (!settings) throw new Error("failed_to_get_settings"); + return settings.get(type, key); + } + + private async updateSetting({ type, key, value }: RequestUpdateSetting) { + const settings = await Settings.get(); + if (!settings) throw new Error("failed_to_get_settings"); + settings.update(type, key, value); + await Settings.set(settings); + } + + private async getContacts(): Promise { + const registry = await Registry.get(); + if (!registry) throw new Error("failed_to_get_registry"); + return registry.getAllContacts(); + } + + private async getRegistryAddresses() { + const registry = await Registry.get(); + if (!registry) throw new Error("failed_to_get_registry"); + const { chain } = await Network.get(); + if (!chain) throw new Error("failed_to_get_network"); + const types = chain.supportedAccounts || []; + const accounts = await AccountManager.getAll(types); + if (!accounts) throw new Error("failed_to_get_accounts"); + return { + ownAccounts: accounts + .getAll() + .map( + (account) => new Contact(account.value.name, account.value.address) + ), + contacts: registry.getAllContacts(), + recent: registry.getRecentAddresses(chain.name), + }; + } + + private async saveContact({ contact }: RequestSaveContact) { + await Registry.addContact(contact); + } + + private async removeContact({ address }: RequestRemoveContact) { + await Registry.removeContact(address); + } + + private async getActivity(): Promise { + return Activity.getRecords(); + } + + private async getAllChains(): Promise { + const newChains = await Chains.loadChains(); + const chains = await Chains.get(); + if (!chains) throw new Error("failed_to_get_chains"); + + if (newChains) { + const network = await Network.get(); + const selectedNetwork = network.chain; + const selectedNetworkInMainnets = newChains.mainnets.find( + (chain) => + chain.nativeCurrency.symbol === selectedNetwork?.nativeCurrency.symbol + ); + + const selectedNetworkInTestnets = newChains.testnets.find( + (chain) => + chain.nativeCurrency.symbol === selectedNetwork?.nativeCurrency.symbol + ); + + let chainToUpdate = null; + + if (selectedNetworkInMainnets) { + chainToUpdate = selectedNetworkInMainnets; + } + + if (selectedNetworkInTestnets) { + chainToUpdate = selectedNetworkInTestnets; + } + + if (chainToUpdate) { + network.chain = chainToUpdate; + await Network.set(network); + } + } + + return chains; + } + + private async saveCustomChain({ chain }: RequestSaveCustomChain) { + await Chains.saveCustomChain(chain); + } + + private async removeCustomChain({ chainName }: RequestRemoveCustomChain) { + await Chains.removeCustomChain(chainName); + } + + private async getXCMChains({ + chainName, + }: RequestGetXCMChains): Promise { + const { xcm } = (await Chains.getByName(chainName)) || {}; + if (!xcm) throw new Error("failed_to_get_chain"); + const chains = await Chains.get(); + if (!chains) throw new Error("failed_to_get_chains"); + return chains.getAll().filter((chain) => xcm.includes(chain.name)); + } + + private async addActivity({ txHash, record }: RequestAddActivity) { + await Activity.addRecord(txHash, record); + const { address, network } = record; + const register = new Register(address, Date.now()); + await Registry.addRecentAddress(network, register); + } + + private async updateActivity({ + txHash, + status, + error, + }: RequestUpdateActivity) { + await Activity.updateRecordStatus(txHash, status, error); + } + + private async addAsset({ chain, asset }: RequestAddAsset) { + return Assets.addAsset(chain, asset); + } + + private async getAssetsByChain({ chain }: RequestGetAssetsByChain) { + return Assets.getByChain(chain); + } + + private async getTrustedSites(): Promise { + return TrustedSites.getAll(); + } + + private async addTrustedSite({ site }: RequestAddTrustedSite) { + return TrustedSites.addSite(site); + } + + private async removeTrustedSite({ site }: RequestRemoveTrustedSite) { + return TrustedSites.removeSite(site); + } + + private async getSwapsByProtocol({ protocol }: RequestSwapProtocol) { + return Swap.getSwapsByProtocol(protocol); + } + + private async addSwap({ protocol, swap }: RequestAddSwap) { + return Swap.addSwap(protocol, swap); + } + + private async sendSubstrateTx({ + amount, + asset, + destinationAddress, + destinationNetwork, + hexExtrinsic, + networkName, + originAddress, + rpc, + }: RequestSendSubstrateTx) { + try { + const provider = (await getProvider(rpc, "wasm")) as ApiPromise; + const seed = await this.showKey(); + const sender = PolkadotKeyring.keyring.addFromMnemonic(seed as string); + const { block } = await provider.rpc.chain.getBlock(); + + const unsub = await provider + .tx(hexExtrinsic) + .signAndSend(sender, async ({ events, txHash, status }) => { + if (String(status.type) === "Ready") { + const hash = txHash.toString(); + const date = Date.now(); + const activity: Partial = { + fromBlock: block.header.number.toString(), + address: destinationAddress, + type: RecordType.TRANSFER, + reference: AccountType.WASM, + hash, + status: RecordStatus.PENDING, + createdAt: date, + lastUpdated: date, + error: undefined, + network: networkName, + recipientNetwork: destinationNetwork, + data: { + from: originAddress, + to: destinationAddress, + gas: "", + gasPrice: "", + symbol: asset.symbol, + value: String(amount), + asset: { + id: asset.id, + // color: asset.color, + }, + }, + }; + await this.addActivity({ + txHash: hash, + record: activity as Record, + }); + this.sendUpdateActivityMessage(); + } + if (status.isFinalized) { + const failedEvents = events.filter(({ event }) => + provider?.events.system.ExtrinsicFailed.is(event) + ); + let status = RecordStatus.PENDING; + let error = undefined; + if (failedEvents.length > 0) { + failedEvents.forEach( + ({ + event: { + data: [_error], + }, + }: { + event: { + data: Partial<{ + isModule: boolean; + asModule: + | Uint8Array + | { + error: BN; + index: BN; + } + | { + error: BN | Uint8Array; + index: BN; + }; + toString: () => string; + }>[]; + }; + }) => { + if (_error.isModule) { + const decoded = provider.registry.findMetaError( + _error.asModule as + | Uint8Array + | { + error: BN; + index: BN; + } + | { + error: BN | Uint8Array; + index: BN; + } + ); + const { docs, method, section } = decoded; + error = `${section}.${method}: ${docs.join(" ")}`; + } else { + error = _error.toString?.(); + } + } + ); + status = RecordStatus.FAIL; + } else { + status = RecordStatus.SUCCESS; + + // swap && (await Extension.addSwap(swap.protocol, { id: swap.id })); + } + const hash = txHash.toString(); + await this.updateActivity({ txHash: hash, status, error }); + this.sendTxNotification({ title: `tx ${status}`, message: hash }); + this.sendUpdateActivityMessage(); + unsub(); + } + }); + + return true; + } catch (error) { + this.sendTxNotification({ + title: "tx error", + message: "", + }); + } + } + + private async sendEvmTx({ + amount, + asset, + destinationAddress, + destinationNetwork, + // fee, + networkName, + originAddress, + rpc, + txHash, + }: RequestSendEvmTx) { + try { + const api = (await getProvider( + rpc, + AccountType.EVM + )) as ethers.providers.JsonRpcProvider; + const txReceipt = await api.getTransaction(txHash); + const date = Date.now(); + const activity: Partial = { + address: destinationAddress, + type: RecordType.TRANSFER, + reference: AccountType.EVM, + hash: txHash, + status: RecordStatus.PENDING, + createdAt: date, + lastUpdated: date, + error: undefined, + network: networkName, + recipientNetwork: destinationNetwork, + data: { + from: originAddress, + to: destinationAddress, + gas: txReceipt.gasLimit.toString(), + gasPrice: txReceipt.gasPrice?.toString() || "", + symbol: asset.symbol, + value: String(amount), + asset: { + id: asset.id, + }, + }, + }; + await this.addActivity({ txHash, record: activity as Record }); + this.sendUpdateActivityMessage(); + const result = await txReceipt.wait(); + const status = + result.status === 1 ? RecordStatus.SUCCESS : RecordStatus.FAIL; + // result.status === 1 && + // swap && + // (await Extension.addSwap(swap.protocol, { id: swap.id })); + const error = ""; + await this.updateActivity({ txHash, status, error }); + this.sendTxNotification({ title: `tx ${status}`, message: txHash }); + this.sendUpdateActivityMessage(); + } catch (error) { + this.sendTxNotification({ title: `tx failed`, message: txHash }); + await this.updateActivity({ + txHash, + status: RecordStatus.FAIL, + error: String(error), + }); + // captureError(error); + } + } + + private sendUpdateActivityMessage() { + WebAPI.runtime.sendMessage({ + origin: "kuma", + method: "update_activity", + }); + } + + private sendTxNotification({ + title, + message, + }: { + title: string; + message: string; + }) { + WebAPI.notifications.create("id", { + title, + message, + iconUrl: notificationIcon, + type: "basic", + }); + } + + async handle( + id: string, + type: TMessageType, + request: RequestTypes[TMessageType] + // port: Port + ): Promise> { + switch (type) { + case "pri(accounts.createAccounts)": + return this.createAccounts(request as RequestCreateAccount); + case "pri(accounts.importAccount)": + return this.importAccount(request as RequestImportAccount); + case "pri(accounts.restorePassword)": + return this.restorePassword(request as RequestRestorePassword); + case "pri(accounts.removeAccount)": + return this.removeAccount(request as RequestRemoveAccout); + case "pri(accounts.changeAccountName)": + return this.changeAccountName(request as RequestChangeAccountName); + case "pri(accounts.areAccountsInitialized)": + return this.areAccountsInitialized(); + case "pri(accounts.getAccount)": + return this.getAccount(request as RequestGetAccount); + case "pri(accounts.getAllAccounts)": + return this.getAllAccounts(request as RequestGetAllAccounts); + case "pri(accounts.deriveAccount)": + return this.deriveAccount(request as RequestDeriveAccount); + case "pri(accounts.setSelectedAccount)": + return this.setSelectedAccount(request as Account); + case "pri(accounts.getSelectedAccount)": + return this.getSelectedAccount(); + + case "pri(auth.isAuthorized)": + return this.isAuthorized(); + case "pri(auth.resetWallet)": + return this.resetWallet(); + case "pri(auth.signIn)": + return this.signIn(request as RequestSignIn); + case "pri(auth.signOut)": + return this.signOut(); + case "pri(auth.alreadySignedUp)": + return this.alreadySignedUp(); + case "pri(auth.isSessionActive)": + return this.isSessionActive(); + case "pri(auth.showKey)": + return this.showKey(); + + case "pri(network.setNetwork)": + return this.setNetwork(request as RequestSetNetwork); + case "pri(network.getNetwork)": + return this.getNetwork(); + case "pri(network.getAllChains)": + return this.getAllChains(); + case "pri(network.saveCustomChain)": + return this.saveCustomChain(request as RequestSaveCustomChain); + case "pri(network.removeCustomChain)": + return this.removeCustomChain(request as RequestRemoveCustomChain); + case "pri(network.getXCMChains)": + return this.getXCMChains(request as RequestGetXCMChains); + + case "pri(settings.getGeneralSettings)": + return this.getGeneralSettings(); + case "pri(settings.getAdvancedSettings)": + return this.getAdvancedSettings(); + case "pri(settings.getSetting)": + return this.getSetting(request as RequestGetSetting); + case "pri(settings.updateSetting)": + return this.updateSetting(request as RequestUpdateSetting); + + case "pri(contacts.getContacts)": + return this.getContacts(); + case "pri(contacts.getRegistryAddresses)": + return this.getRegistryAddresses(); + case "pri(contacts.saveContact)": + return this.saveContact(request as RequestSaveContact); + case "pri(contacts.removeContact)": + return this.removeContact(request as RequestRemoveContact); + + case "pri(activity.getActivity)": + return this.getActivity(); + case "pri(activity.addActivity)": + return this.addActivity(request as RequestAddActivity); + case "pri(activity.updateActivity)": + return this.updateActivity(request as RequestUpdateActivity); + + case "pri(assets.addAsset)": + return this.addAsset(request as RequestAddAsset); + case "pri(assets.getAssetsByChain)": + return this.getAssetsByChain(request as RequestGetAssetsByChain); + + case "pri(trustedSites.getTrustedSites)": + return this.getTrustedSites(); + case "pri(trustedSites.addTrustedSite)": + return this.addTrustedSite(request as RequestAddTrustedSite); + case "pri(trustedSites.removeTrustedSite)": + return this.removeTrustedSite(request as RequestRemoveTrustedSite); + + case "pri(swap.getSwapsByProtocol)": + return this.getSwapsByProtocol(request as RequestSwapProtocol); + case "pri(swap.addSwap)": + return this.addSwap(request as RequestAddSwap); + + case "pri(send.sendSubstrateTx)": + return this.sendSubstrateTx(request as RequestSendSubstrateTx); + case "pri(send.sendEvmTx)": + return this.sendEvmTx(request as RequestSendEvmTx); + + default: + throw new Error(`Unable to handle message of type ${type}`); + } + } +} diff --git a/src/entries/background/handlers/kumaHandler.ts b/src/entries/background/handlers/kumaHandler.ts new file mode 100644 index 00000000..5fa89e50 --- /dev/null +++ b/src/entries/background/handlers/kumaHandler.ts @@ -0,0 +1,85 @@ +import Extension from "./Extension"; +import { Port, TransportRequestMessage } from "./types"; +// import { PORT_EXTENSION } from "@src/constants/env"; +import { assert } from "@polkadot/util"; +import { MessageTypes } from "./request-types"; + +const extension = new Extension(); + +const kumaHandler = ( + data: TransportRequestMessage, + port: Port + // extensionPortName = PORT_EXTENSION +) => { + const { id, message, request } = data; + // const isExtension = port.name === extensionPortName; + // const sender = port.sender as chrome.runtime.MessageSender; + // const from = isExtension ? "extension" : sender?.tab?.url || ""; + + // handle the request and get a promise as a response + // const promise = isExtension + // ? extension.handle(id, message, request, port) + // : tabs.handle(id, message, request, port, from); + + const promise = extension.handle(id, message, request); + + promise + .then((response) => { + assert(port, "Port has been disconnected"); + + try { + port.postMessage({ id, response }); + } catch (e) { + if ( + e instanceof Error && + e.message === "Attempting to use a disconnected port object" + ) { + // this means that the user has done something like close the tab + port.disconnect(); + return; + } + throw e; + } + + // heap cleanup + response = undefined; + }) + .catch((error) => { + if ( + error instanceof Error && + error.message === "Attempting to use a disconnected port object" + ) { + // this means that the user has done something like close the tab + port.disconnect(); + return; + } + + // only send message back to port if it's still connected, unfortunately this check is not reliable in all browsers + if (port) { + try { + if (["pub(eth.request)", "pri(eth.request)"].includes(message)) + port.postMessage({ + id, + error: error.message, + code: error.code, + data: error.data, + isEthProviderRpcError: true, + }); + else port.postMessage({ id, error: error.message }); + } catch (caughtError) { + /** + * no-op + * caughtError will be `Attempt to postMessage on disconnected port` + * The original errors themselves are mostly intentionally thrown as control flow for dapp connections, so logging them creates noise + * */ + } + } + }) + .finally(() => { + // heap cleanup + // @ts-expect-error -- * + data.request = null; + }); +}; + +export default kumaHandler; diff --git a/src/entries/background/handlers/request-types.ts b/src/entries/background/handlers/request-types.ts new file mode 100644 index 00000000..828ff34f --- /dev/null +++ b/src/entries/background/handlers/request-types.ts @@ -0,0 +1,256 @@ +import { AccountKey, AccountType } from "@src/accounts/types"; +import Account from "@src/storage/entities/Account"; +import Chains, { Chain } from "@src/storage/entities/Chains"; +import Network from "@src/storage/entities/Network"; +import Record from "@src/storage/entities/activity/Record"; +import { RecordStatus } from "@src/storage/entities/activity/types"; +import Contact from "@src/storage/entities/registry/Contact"; +import Register from "@src/storage/entities/registry/Register"; +import { SwapData } from "@src/storage/entities/registry/Swap"; +import Setting from "@src/storage/entities/settings/Setting"; +import { + SettingKey, + SettingType, + SettingValue, +} from "@src/storage/entities/settings/types"; + +export interface RequestSignUp { + password: string; + privateKeyOrSeed: string; +} + +export interface RequestCreateAccount { + seed: string; + name: string; + password: string | undefined; + isSignUp: boolean | undefined; +} + +export interface RequestImportAccount { + name: string; + privateKeyOrSeed: string; + password: string | undefined; + type: AccountType.IMPORTED_EVM | AccountType.IMPORTED_WASM; + isSignUp: boolean | undefined; +} + +export interface RequestRestorePassword { + privateKeyOrSeed: string; + newPassword: string; +} + +export interface RequestRemoveAccout { + key: AccountKey; +} + +export interface RequestChangeAccountName { + key: AccountKey; + newName: string; +} + +export interface RequestSignIn { + password: string; +} + +export interface RequestGetAccount { + key: AccountKey; +} + +export interface RequestGetAllAccounts { + type: AccountType[] | null; +} + +export interface RequestDeriveAccount { + name: string; + type: AccountType; +} + +export interface RequestSetNetwork { + chain: Chain; +} + +export interface RequestSaveCustomChain { + chain: Chain; +} + +export interface RequestRemoveCustomChain { + chainName: string; +} + +export interface RequestGetXCMChains { + chainName: string; +} + +export interface RequestGetSetting { + type: SettingType; + key: SettingKey; +} + +export interface RequestUpdateSetting { + type: SettingType; + key: SettingKey; + value: SettingValue; +} + +export interface RequestSaveContact { + contact: Contact; +} + +export interface RequestRemoveContact { + address: string; +} + +export interface RequestAddActivity { + txHash: string; + record: Record; +} + +export interface RequestUpdateActivity { + txHash: string; + status: RecordStatus; + error?: string | undefined; +} + +export interface RequestAddAsset { + chain: string; + asset: { + symbol: string; + address: string; + decimals: number; + }; +} + +export interface RequestGetAssetsByChain { + chain: string; +} + +export interface RequestAddTrustedSite { + site: string; +} + +export interface RequestRemoveTrustedSite { + site: string; +} + +export interface RequestSwapProtocol { + protocol: string; +} + +export interface RequestAddSwap { + protocol: string; + swap: SwapData; +} + +interface RequestSendTxBase { + amount: string; + asset: { + symbol: string; + id: string; + }; + originAddress: string; + destinationAddress: string; + destinationNetwork: string; + networkName: string; + rpc: string; +} + +export interface RequestSendSubstrateTx extends RequestSendTxBase { + hexExtrinsic: string; +} + +export interface RequestSendEvmTx extends RequestSendTxBase { + txHash: string; + fee: { + gasLimit: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + type?: number; + }; +} + +export interface Request { + "pri(accounts.createAccounts)": [RequestCreateAccount, boolean]; + "pri(accounts.importAccount)": [RequestImportAccount, void]; + "pri(accounts.restorePassword)": [RequestRestorePassword, void]; + "pri(accounts.removeAccount)": [RequestRemoveAccout, void]; + "pri(accounts.changeAccountName)": [RequestChangeAccountName, void]; + "pri(accounts.areAccountsInitialized)": [null, boolean]; + "pri(accounts.getAccount)": [RequestGetAccount, Account | undefined]; + "pri(accounts.getAllAccounts)": [RequestGetAllAccounts, Account[]]; + "pri(accounts.deriveAccount)": [RequestDeriveAccount, Account]; + "pri(accounts.setSelectedAccount)": [Account, void]; + + "pri(accounts.getSelectedAccount)": [null, Account | undefined]; + + "pri(auth.isAuthorized)": [null, boolean]; + "pri(auth.resetWallet)": [null, void]; + "pri(auth.signIn)": [RequestSignIn, void]; + "pri(auth.signOut)": [null, void]; + "pri(auth.alreadySignedUp)": [null, boolean]; + "pri(auth.isSessionActive)": [null, boolean]; + "pri(auth.showKey)": [null, string | undefined]; + + "pri(network.setNetwork)": [RequestSetNetwork, boolean]; + "pri(network.getNetwork)": [null, Network]; + "pri(network.getAllChains)": [null, Chains]; + "pri(network.saveCustomChain)": [RequestSaveCustomChain, void]; + "pri(network.removeCustomChain)": [RequestRemoveCustomChain, void]; + "pri(network.getXCMChains)": [RequestGetXCMChains, Chain[]]; + + "pri(settings.getGeneralSettings)": [null, Setting[]]; + "pri(settings.getAdvancedSettings)": [null, Setting[]]; + "pri(settings.getSetting)": [RequestGetSetting, Setting | undefined]; + "pri(settings.updateSetting)": [RequestUpdateSetting, void]; + + "pri(contacts.getContacts)": [null, Contact[]]; + "pri(contacts.getRegistryAddresses)": [ + null, + { + ownAccounts: Contact[]; + contacts: Contact[]; + recent: Register[]; + } + ]; + "pri(contacts.saveContact)": [RequestSaveContact, void]; + "pri(contacts.removeContact)": [RequestRemoveContact, void]; + + "pri(activity.getActivity)": [null, Record[]]; + "pri(activity.addActivity)": [RequestAddActivity, void]; + "pri(activity.updateActivity)": [RequestUpdateActivity, void]; + + "pri(assets.addAsset)": [RequestAddAsset, void]; + "pri(assets.getAssetsByChain)": [ + RequestGetAssetsByChain, + { + symbol: string; + address: string; + decimals: number; + }[] + ]; + + "pri(trustedSites.getTrustedSites)": [null, string[]]; + "pri(trustedSites.addTrustedSite)": [RequestAddTrustedSite, void]; + "pri(trustedSites.removeTrustedSite)": [RequestRemoveTrustedSite, void]; + + "pri(swap.getSwapsByProtocol)": [RequestSwapProtocol, SwapData[]]; + "pri(swap.addSwap)": [RequestAddSwap, void]; + + "pri(send.sendSubstrateTx)": [RequestSendSubstrateTx, boolean]; + "pri(send.sendEvmTx)": [RequestSendEvmTx, boolean]; +} + +export type MessageTypes = keyof Request; + +export type RequestTypes = { + [MessageType in keyof Request]: Request[MessageType][0]; +}; + +export declare type RequestType = + Request[TMessageType][0]; + +export type ResponseTypes = { + [MessageType in keyof Request]: Request[MessageType][1]; +}; + +export type ResponseType = + Request[TMessageType][1]; diff --git a/src/entries/background/handlers/types.ts b/src/entries/background/handlers/types.ts new file mode 100644 index 00000000..f94dbe95 --- /dev/null +++ b/src/entries/background/handlers/types.ts @@ -0,0 +1,58 @@ +import { + MessageTypes, + RequestType, + RequestTypes, + ResponseType, +} from "./request-types"; + +export type Port = chrome.runtime.Port; + +export declare type OriginTypes = "kuma-page" | "kuma-extension"; + +export interface TransportRequestMessage { + id: string; + message: TMessageType; + origin: OriginTypes; + request: RequestTypes[TMessageType]; +} + +interface Handler { + handle( + id: string, + type: TMessageType, + request: RequestTypes[TMessageType], + port: Port, + url?: string + ): Promise>; +} + +abstract class HandlerBase implements Handler { + public abstract handle( + id: string, + type: TMessageType, + request: RequestType, + port: Port, + url?: string + ): Promise>; +} + +export abstract class TabsHandler extends HandlerBase { + // TODO: abstract handle + + abstract handle( + id: string, + type: TMessageType, + request: RequestTypes[TMessageType], + port: Port, + url: string + ): Promise>; +} + +export abstract class ExtensionHandler extends HandlerBase { + abstract handle( + id: string, + type: TMessageType, + request: RequestTypes[TMessageType], + port: Port + ): Promise>; +} diff --git a/src/entries/background/index.ts b/src/entries/background/index.ts index 75a3b28e..294ea78b 100644 --- a/src/entries/background/index.ts +++ b/src/entries/background/index.ts @@ -1,26 +1,9 @@ -import { AccountType } from "@src/accounts/types"; -import { default as IRecord } from "@src/storage/entities/activity/Record"; -import { - RecordStatus, - RecordType, - TransferData, -} from "@src/storage/entities/activity/types"; -import { makeQuerys } from "@src/utils/utils"; -import { ethers } from "ethers"; -import Extension from "@src/Extension"; -import notificationIcon from "/icon-128.png"; -import { ApiPromise, WsProvider } from "@polkadot/api"; -import { TxToProcess } from "@src/types"; -import { BN } from "@polkadot/util"; -import { captureError } from "@src/utils/error-handling"; - -export const getProvider = (rpc: string, type: string) => { - if (type.toLowerCase() === "evm") - return new ethers.providers.JsonRpcProvider(rpc as string); - - if (type.toLowerCase() === "wasm") - return ApiPromise.create({ provider: new WsProvider(rpc as string) }); -}; +import { cryptoWaitReady } from "@polkadot/util-crypto"; +import { PORT_CONTENT, PORT_EXTENSION } from "@src/constants/env"; +import PolkadotKeyring from "@polkadot/ui-keyring"; +import { AccountsStore } from "@polkadot/extension-base/stores"; +import kumaHandler from "./handlers/kumaHandler"; +import { assert } from "@polkadot/util"; const getWebAPI = (): typeof chrome => { return navigator.userAgent.match(/chrome|chromium|crios/i) @@ -28,311 +11,35 @@ const getWebAPI = (): typeof chrome => { : window.browser; }; -const WebAPI = getWebAPI(); +const webAPI = getWebAPI(); -const openPopUp = (params: Record) => { - const querys = makeQuerys(params); +// listen to all messages and handle appropriately +webAPI.runtime.onConnect.addListener((_port): void => { + // only listen to what we know about + assert( + [PORT_CONTENT, PORT_EXTENSION].includes(_port.name), + `Unknown connection from ${_port.name}` + ); + let port: chrome.runtime.Port | undefined = _port; - return WebAPI.windows.create({ - url: WebAPI.runtime.getURL(`src/entries/popup/index.html${querys}`), // ?method="sign_message¶ms={}" - type: "popup", - top: 0, - left: 0, - width: 357, - height: 600, - focused: true, + port.onDisconnect.addListener(() => { + port = undefined; }); -}; - -// read messages from content -WebAPI.runtime.onMessage.addListener(async function (request, sender) { - if (request.origin === "kuma") { - try { - switch (request.method) { - case "process_tx": { - processTx(request.tx); - return; - } - - case "call_contract": - case "sign_message": { - await openPopUp({ ...request, tabId: sender.tab?.id }); - return; - } - - case "call_contract_response": - case "sign_message_response": { - if (request.from !== "popup") return; - await WebAPI.tabs.sendMessage(Number(request.toTabId), { - ...request, - from: "bg", - }); - if (request.fromWindowId) - await WebAPI.windows.remove(request.fromWindowId as number); - - return; - } - case "get_account_info": { - getSelectedAccount().then(async (res) => { - await WebAPI.tabs.sendMessage(Number(sender.tab?.id), { - origin: "kuma", - method: `${request.method}_response`, - response: { - address: res?.value.address, - type: res?.type, - }, - from: "bg", - }); - }); - return; - } - - default: - break; - } - } catch (error) { - await WebAPI.tabs.sendMessage(Number(request.toTabId), { - ...{ - ...request, - method: `${request.method}_response`, - }, - from: "bg", - error, - }); - return error; - } - - return true; - } + port.onMessage.addListener((data) => { + if (port) kumaHandler(data, port); + }); }); -WebAPI.runtime.onConnect.addListener(function (port) { - if (port.name === "sign_message") { - port.onDisconnect.addListener(async function (port) { - const queries = port.sender?.url?.split("?")[1]; - const { tabId, origin, method } = Object.fromEntries( - new URLSearchParams(queries) - ); - await WebAPI.tabs.sendMessage(Number(tabId), { - origin, - method: `${method}_response`, - response: null, - from: "bg", - }); +// Init polkadot +cryptoWaitReady() + .then((): void => { + PolkadotKeyring.loadAll({ + store: new AccountsStore(), + type: "sr25519", }); - } -}); - -const getSelectedAccount = () => { - return Extension.getSelectedAccount(); -}; - -const processTx = (tx: TxToProcess) => { - if (tx?.tx.type === AccountType.WASM) { - processWasmTx(tx); - } else { - processEVMTx(tx); - } -}; - -const processWasmTx = async ({ - amount, - asset, - originAddress, - destinationAddress, - // originNetwork, - destinationNetwork, - networkInfo, - tx, - rpc, - swap, -}: TxToProcess) => { - try { - const { txHash, type } = tx; - const api = (await getProvider(rpc, AccountType.WASM)) as ApiPromise; - const { block } = await api.rpc.chain.getBlock(); - - const unsub = await api - ?.tx(txHash) - ?.send(async ({ events, txHash, status }) => { - if (String(status.type) === "Ready") { - const hash = txHash.toString(); - const date = Date.now(); - const activity: Partial = { - fromBlock: block.header.number.toString(), - address: destinationAddress, - type: RecordType.TRANSFER, - reference: type, - hash, - status: RecordStatus.PENDING, - createdAt: date, - lastUpdated: date, - error: undefined, - network: networkInfo.name, - recipientNetwork: destinationNetwork, - data: { - from: originAddress, - to: destinationAddress, - gas: "", - gasPrice: "", - symbol: asset.symbol, - value: String(amount), - asset: { - id: asset.id, - color: asset.color, - }, - } as TransferData, - }; - await Extension.addActivity(hash, activity as IRecord); - sendUpdateActivityMessage(); - } - if (status.isFinalized) { - const failedEvents = events.filter(({ event }) => - api?.events.system.ExtrinsicFailed.is(event) - ); - let status = RecordStatus.PENDING; - let error = undefined; - if (failedEvents.length > 0) { - failedEvents.forEach( - ({ - event: { - data: [_error], - }, - }: { - event: { - data: Partial<{ - isModule: boolean; - asModule: - | Uint8Array - | { - error: BN; - index: BN; - } - | { - error: BN | Uint8Array; - index: BN; - }; - toString: () => string; - }>[]; - }; - }) => { - if (_error.isModule) { - const decoded = api.registry.findMetaError( - _error.asModule as - | Uint8Array - | { - error: BN; - index: BN; - } - | { - error: BN | Uint8Array; - index: BN; - } - ); - const { docs, method, section } = decoded; - error = `${section}.${method}: ${docs.join(" ")}`; - } else { - error = _error.toString?.(); - } - } - ); - status = RecordStatus.FAIL; - } else { - status = RecordStatus.SUCCESS; - - swap && (await Extension.addSwap(swap.protocol, { id: swap.id })); - } - const hash = txHash.toString(); - await Extension.updateActivity(hash, status, error); - sendNotification(`tx ${status}`, hash); - sendUpdateActivityMessage(); - unsub(); - } - }); - } catch (error) { - sendNotification(`tx error`, ""); - } -}; - -const processEVMTx = async ({ - amount, - asset, - originAddress, - destinationAddress, - // originNetwork, - destinationNetwork, - networkInfo, - tx, - rpc, - swap, -}: TxToProcess) => { - const { txHash } = tx; - try { - const api = (await getProvider( - rpc, - AccountType.EVM - )) as ethers.providers.JsonRpcProvider; - const txReceipt = await api.getTransaction(txHash); - const date = Date.now(); - - const activity: Partial = { - address: destinationAddress, - type: RecordType.TRANSFER, - reference: AccountType.EVM, - hash: txHash, - status: RecordStatus.PENDING, - createdAt: date, - lastUpdated: date, - error: undefined, - network: networkInfo.name, - recipientNetwork: destinationNetwork, - data: { - from: originAddress, - to: destinationAddress, - gas: "", - gasPrice: "", - symbol: asset.symbol, - value: String(amount), - asset: { - id: asset.id, - color: asset.color, - }, - } as TransferData, - }; - await Extension.addActivity(txHash, activity as IRecord); - sendUpdateActivityMessage(); - const result = await txReceipt.wait(); - const status = - result.status === 1 ? RecordStatus.SUCCESS : RecordStatus.FAIL; - - result.status === 1 && - swap && - (await Extension.addSwap(swap.protocol, { id: swap.id })); - - const error = ""; - await Extension.updateActivity(txHash, status, error); - sendNotification(`tx ${status}`, txHash); - sendUpdateActivityMessage(); - } catch (error) { - sendNotification(`tx failed`, txHash); - await Extension.updateActivity(txHash, RecordStatus.FAIL, String(error)); - captureError(error); - } -}; - -const sendNotification = (title: string, message: string) => { - WebAPI.notifications.create("id", { - title, - message, - iconUrl: notificationIcon, - type: "basic", + console.log("polkadot keyring loaded"); + }) + .catch((error): void => { + console.error("initialization failed", error); }); -}; - -const sendUpdateActivityMessage = () => { - WebAPI.runtime.sendMessage({ - origin: "kuma", - method: "update_activity", - }); -}; diff --git a/src/main.tsx b/src/main.tsx index c8510c37..623ef9f5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,6 @@ import { AssetProvider, AccountProvider, - AuthProvider, NetworkProvider, TxProvider, ThemeProvider, @@ -9,7 +8,6 @@ import { import { Routes } from "./routes"; import { ToastContainer } from "react-toastify"; import "./utils/i18n"; -import "./utils/polkadot-keyring"; import "react-toastify/dist/ReactToastify.min.css"; import * as Sentry from "@sentry/react"; import { Error } from "./components/common"; @@ -21,20 +19,18 @@ Sentry.init({ export const Main = () => { return ( }> - - - - - - - - - - - - - - + + + + + + + + + + + + ); }; diff --git a/src/messageAPI/accounts.ts b/src/messageAPI/accounts.ts new file mode 100644 index 00000000..fa9a1d86 --- /dev/null +++ b/src/messageAPI/accounts.ts @@ -0,0 +1,48 @@ +import { + RequestChangeAccountName, + RequestCreateAccount, + RequestDeriveAccount, + RequestGetAccount, + RequestGetAllAccounts, + RequestImportAccount, + RequestRemoveAccout, + RequestRestorePassword, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; +import Account from "@src/storage/entities/Account"; + +export const accountMessages = { + createAccounts: (params: RequestCreateAccount): Promise => { + return sendMessage("pri(accounts.createAccounts)", params); + }, + importAccount: (params: RequestImportAccount) => { + return sendMessage("pri(accounts.importAccount)", params); + }, + restorePassword: (params: RequestRestorePassword) => { + return sendMessage("pri(accounts.restorePassword)", params); + }, + removeAccount: (params: RequestRemoveAccout) => { + return sendMessage("pri(accounts.removeAccount)", params); + }, + changeAccountName: (params: RequestChangeAccountName) => { + return sendMessage("pri(accounts.changeAccountName)", params); + }, + areAccountsInitialized: () => { + return sendMessage("pri(accounts.areAccountsInitialized)", null); + }, + getAccount: (params: RequestGetAccount) => { + return sendMessage("pri(accounts.getAccount)", params); + }, + getAllAccounts: (params: RequestGetAllAccounts) => { + return sendMessage("pri(accounts.getAllAccounts)", params); + }, + deriveAccount: (params: RequestDeriveAccount) => { + return sendMessage("pri(accounts.deriveAccount)", params); + }, + setSelectedAccount: (params: Account) => { + return sendMessage("pri(accounts.setSelectedAccount)", params); + }, + getSelectedAccount: () => { + return sendMessage("pri(accounts.getSelectedAccount)", null); + }, +}; diff --git a/src/messageAPI/activity.ts b/src/messageAPI/activity.ts new file mode 100644 index 00000000..b66b7457 --- /dev/null +++ b/src/messageAPI/activity.ts @@ -0,0 +1,17 @@ +import { + RequestAddActivity, + RequestUpdateActivity, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const activityMessages = { + getActivity: () => { + return sendMessage("pri(activity.getActivity)", null); + }, + addActivity: (params: RequestAddActivity) => { + return sendMessage("pri(activity.addActivity)", params); + }, + updateActivity: (params: RequestUpdateActivity) => { + return sendMessage("pri(activity.updateActivity)", params); + }, +}; diff --git a/src/messageAPI/api.ts b/src/messageAPI/api.ts new file mode 100644 index 00000000..21bc691c --- /dev/null +++ b/src/messageAPI/api.ts @@ -0,0 +1,23 @@ +import { accountMessages } from "./accounts"; +import { activityMessages } from "./activity"; +import { authMessages } from "./auth"; +import { contactsMessages } from "./contacts"; +import { networkMessages } from "./network"; +import { assetsMessages } from "./assets"; +import { settingsMessages } from "./settings"; +import { trustedSitesMessages } from "./trustedSites"; +import { swapMessages } from "./swap"; +import { sendMessages } from "./send"; + +export const messageAPI = { + ...accountMessages, + ...activityMessages, + ...authMessages, + ...contactsMessages, + ...networkMessages, + ...assetsMessages, + ...settingsMessages, + ...trustedSitesMessages, + ...swapMessages, + ...sendMessages, +}; diff --git a/src/messageAPI/assets.ts b/src/messageAPI/assets.ts new file mode 100644 index 00000000..c8a8ab1a --- /dev/null +++ b/src/messageAPI/assets.ts @@ -0,0 +1,14 @@ +import { + RequestAddAsset, + RequestGetAssetsByChain, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const assetsMessages = { + addAsset: (params: RequestAddAsset) => { + return sendMessage("pri(assets.addAsset)", params); + }, + getAssetsByChain: (params: RequestGetAssetsByChain) => { + return sendMessage("pri(assets.getAssetsByChain)", params); + }, +}; diff --git a/src/messageAPI/auth.ts b/src/messageAPI/auth.ts new file mode 100644 index 00000000..84c6708b --- /dev/null +++ b/src/messageAPI/auth.ts @@ -0,0 +1,26 @@ +import { RequestSignIn } from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const authMessages = { + isAuthorized: () => { + return sendMessage("pri(auth.isAuthorized)", null); + }, + resetWallet: () => { + return sendMessage("pri(auth.resetWallet)", null); + }, + signIn: (params: RequestSignIn) => { + return sendMessage("pri(auth.signIn)", params); + }, + signOut: () => { + return sendMessage("pri(auth.signOut)", null); + }, + alreadySignedUp: () => { + return sendMessage("pri(auth.alreadySignedUp)", null); + }, + isSessionActive: () => { + return sendMessage("pri(auth.isSessionActive)", null); + }, + showKey: () => { + return sendMessage("pri(auth.showKey)", null); + }, +}; diff --git a/src/messageAPI/contacts.ts b/src/messageAPI/contacts.ts new file mode 100644 index 00000000..e21ee994 --- /dev/null +++ b/src/messageAPI/contacts.ts @@ -0,0 +1,20 @@ +import { + RequestRemoveContact, + RequestSaveContact, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const contactsMessages = { + getContacts: () => { + return sendMessage("pri(contacts.getContacts)", null); + }, + getRegistryAddresses: () => { + return sendMessage("pri(contacts.getRegistryAddresses)", null); + }, + saveContact: (params: RequestSaveContact) => { + return sendMessage("pri(contacts.saveContact)", params); + }, + removeContact: (params: RequestRemoveContact) => { + return sendMessage("pri(contacts.removeContact)", params); + }, +}; diff --git a/src/messageAPI/index.ts b/src/messageAPI/index.ts new file mode 100644 index 00000000..9a87949d --- /dev/null +++ b/src/messageAPI/index.ts @@ -0,0 +1,75 @@ +import { PORT_EXTENSION } from "@src/constants/env"; +import { + MessageTypes, + RequestTypes, + ResponseTypes, +} from "@src/entries/background/handlers/request-types"; +import { getWebAPI } from "@src/utils/env"; +import { v4 as uuidv4 } from "uuid"; + +const getId = () => { + return uuidv4(); +}; + +interface Handler { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + resolve: (data: any) => void; + reject: (error: Error) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + subscriber?: (data: any) => void; +} + +type Handlers = Record; + +const webAPI = getWebAPI(); + +const port = webAPI.runtime.connect({ name: PORT_EXTENSION }); +const handlers: Handlers = {}; + +export const sendMessage = ( + message: TMessageType, + request: RequestTypes[TMessageType] +): Promise => { + return new Promise((resolve, reject) => { + const id = getId(); + + handlers[id] = { + resolve, + reject, + }; + + const transportMessage = { + id, + message, + origin, + request, + }; + + port.postMessage(transportMessage); + }); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +port.onMessage.addListener((data: any): void => { + const handler = handlers[data.id]; + + if (!handler) { + console.error(`Unknown response: ${JSON.stringify(data)}`); + + return; + } + + // if (!handler.subscriber) { + // delete handlers[data.id]; + // } + + // if (data.subscription) { + // // eslint-disable-next-line @typescript-eslint/ban-types + // (handler.subscriber as Function)(data.subscription); + // } else + if (data.error) { + handler.reject(new Error(data.error)); + } else { + handler.resolve(data.response); + } +}); diff --git a/src/messageAPI/network.ts b/src/messageAPI/network.ts new file mode 100644 index 00000000..38e3d6e0 --- /dev/null +++ b/src/messageAPI/network.ts @@ -0,0 +1,28 @@ +import { + RequestGetXCMChains, + RequestRemoveCustomChain, + RequestSaveCustomChain, + RequestSetNetwork, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const networkMessages = { + setNetwork: (params: RequestSetNetwork) => { + return sendMessage("pri(network.setNetwork)", params); + }, + getNetwork: () => { + return sendMessage("pri(network.getNetwork)", null); + }, + getAllChains: () => { + return sendMessage("pri(network.getAllChains)", null); + }, + saveCustomChain: (params: RequestSaveCustomChain) => { + return sendMessage("pri(network.saveCustomChain)", params); + }, + removeCustomChain: (params: RequestRemoveCustomChain) => { + return sendMessage("pri(network.removeCustomChain)", params); + }, + getXCMChains: (params: RequestGetXCMChains) => { + return sendMessage("pri(network.getXCMChains)", params); + }, +}; diff --git a/src/messageAPI/send.ts b/src/messageAPI/send.ts new file mode 100644 index 00000000..ead97563 --- /dev/null +++ b/src/messageAPI/send.ts @@ -0,0 +1,14 @@ +import { + RequestSendEvmTx, + RequestSendSubstrateTx, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const sendMessages = { + sendSubstrateTx: (params: RequestSendSubstrateTx) => { + return sendMessage("pri(send.sendSubstrateTx)", params); + }, + sendEvmTx: (params: RequestSendEvmTx) => { + return sendMessage("pri(send.sendEvmTx)", params); + }, +}; diff --git a/src/messageAPI/settings.ts b/src/messageAPI/settings.ts new file mode 100644 index 00000000..63209c92 --- /dev/null +++ b/src/messageAPI/settings.ts @@ -0,0 +1,20 @@ +import { + RequestGetSetting, + RequestUpdateSetting, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const settingsMessages = { + getGeneralSettings: () => { + return sendMessage("pri(settings.getGeneralSettings)", null); + }, + getAdvancedSettings: () => { + return sendMessage("pri(settings.getAdvancedSettings)", null); + }, + getSetting: (params: RequestGetSetting) => { + return sendMessage("pri(settings.getSetting)", params); + }, + updateSetting: (params: RequestUpdateSetting) => { + return sendMessage("pri(settings.updateSetting)", params); + }, +}; diff --git a/src/messageAPI/swap.ts b/src/messageAPI/swap.ts new file mode 100644 index 00000000..c0fc52a3 --- /dev/null +++ b/src/messageAPI/swap.ts @@ -0,0 +1,14 @@ +import { + RequestAddSwap, + RequestSwapProtocol, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const swapMessages = { + getSwapsByProtocol: (params: RequestSwapProtocol) => { + return sendMessage("pri(swap.getSwapsByProtocol)", params); + }, + addSwap: (params: RequestAddSwap) => { + return sendMessage("pri(swap.addSwap)", params); + }, +}; diff --git a/src/messageAPI/trustedSites.ts b/src/messageAPI/trustedSites.ts new file mode 100644 index 00000000..a8de1cf5 --- /dev/null +++ b/src/messageAPI/trustedSites.ts @@ -0,0 +1,17 @@ +import { + RequestAddTrustedSite, + RequestRemoveTrustedSite, +} from "@src/entries/background/handlers/request-types"; +import { sendMessage } from "."; + +export const trustedSitesMessages = { + getTrustedSites: () => { + return sendMessage("pri(trustedSites.getTrustedSites)", null); + }, + addTrustedSite: (params: RequestAddTrustedSite) => { + return sendMessage("pri(trustedSites.addTrustedSite)", params); + }, + removeTrustedSite: (params: RequestRemoveTrustedSite) => { + return sendMessage("pri(trustedSites.removeTrustedSite)", params); + }, +}; diff --git a/src/pages/accountForm/AccountForm.test.tsx b/src/pages/accountForm/AccountForm.test.tsx index 283c91bb..4c4b28d8 100644 --- a/src/pages/accountForm/AccountForm.test.tsx +++ b/src/pages/accountForm/AccountForm.test.tsx @@ -95,6 +95,25 @@ describe("AccountForm", () => { color: "red", }), })); + + + vi.mock("@src/utils/env", () => ({ + version: "1.0.0", + getWebAPI: () => ({ + tabs: { + getCurrent: () => Promise.resolve(undefined), + create: () => vi.fn(), + }, + runtime: { + getURL: vi.fn(), + connect: vi.fn().mockReturnValue({ + onMessage: { + addListener: vi.fn(), + }, + }), + }, + }), + })) }); describe("import account", () => { diff --git a/src/pages/accountForm/AccountForm.tsx b/src/pages/accountForm/AccountForm.tsx index 6437ffb9..ea3b9ec6 100644 --- a/src/pages/accountForm/AccountForm.tsx +++ b/src/pages/accountForm/AccountForm.tsx @@ -201,7 +201,7 @@ export const AccountForm: FC = ({ ); return ( - + {!signUp && !resetPassword && }
diff --git a/src/pages/balance/components/AccountList.test.tsx b/src/pages/balance/components/AccountList.test.tsx index e34cc3fd..c213fb78 100644 --- a/src/pages/balance/components/AccountList.test.tsx +++ b/src/pages/balance/components/AccountList.test.tsx @@ -91,6 +91,24 @@ describe("AccountList", () => { vi.mock("react-router-dom", () => ({ useNavigate: () => vi.fn(), })); + + vi.mock("@src/utils/env", () => ({ + version: "1.0.0", + getWebAPI: () => ({ + tabs: { + getCurrent: () => Promise.resolve(undefined), + create: () => vi.fn(), + }, + runtime: { + getURL: vi.fn(), + connect: vi.fn().mockReturnValue({ + onMessage: { + addListener: vi.fn(), + }, + }), + }, + }), + })) }); it("should render accounts", async () => { diff --git a/src/pages/balance/components/Activity.test.tsx b/src/pages/balance/components/Activity.test.tsx index 12248bea..17d6d7d9 100644 --- a/src/pages/balance/components/Activity.test.tsx +++ b/src/pages/balance/components/Activity.test.tsx @@ -44,29 +44,30 @@ describe("Actvity", () => { }), })); - vi.mock("@src/Extension"); + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { + getAllChains: vi.fn().mockReturnValue( + { + getAll: vi.fn().mockReturnValue([ + { + name: "test", + explorer: { + evm: "http://test.com", + wasm: "wss://test.com", + }, + }, + ]), + } + ), + getRegistryAddresses: vi.fn().mockReturnValue({ + contacts: [], + ownAccounts: [], + }), + } + })); }); it("should render", async () => { - const Extension = (await import("@src/Extension")).default; - - Extension.getAllChains = vi.fn().mockReturnValue({ - getAll: vi.fn().mockReturnValue([ - { - name: "test", - explorer: { - evm: "http://test.com", - wasm: "wss://test.com", - }, - }, - ]), - }); - - Extension.getRegistryAddresses = vi.fn().mockReturnValue({ - contacts: [], - ownAccounts: [], - }); - const { getByTestId } = renderComponent(); await waitFor(() => { expect(getByTestId("search-input")).toBeDefined(); diff --git a/src/pages/balance/components/Activity.tsx b/src/pages/balance/components/Activity.tsx index d2ac019d..7d8d3e66 100644 --- a/src/pages/balance/components/Activity.tsx +++ b/src/pages/balance/components/Activity.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useToast } from "@src/hooks"; -import Extension from "@src/Extension"; import { Loading } from "@src/components/common"; import { RecordData, RecordStatus } from "@src/storage/entities/activity/types"; import { BsArrowUpRight } from "react-icons/bs"; @@ -13,9 +12,10 @@ import { useAccountContext, useThemeContext, } from "@src/providers"; -import Chains from "@src/storage/entities/Chains"; +import { Chain } from "@src/storage/entities/Chains"; import { FaChevronRight } from "react-icons/fa"; import { NetworkIcon } from "./NetworkIcon"; +import { messageAPI } from "@src/messageAPI/api"; const chipColor = { [RecordStatus.FAIL]: "bg-red-600", @@ -42,7 +42,7 @@ export const Activity = () => { const { t: tCommon } = useTranslation("common"); const [isLoading, setIsLoading] = useState(true); const [search, setSearch] = useState("" as string); - const [networks, setNetworks] = useState({} as Chains); + const [networks, setNetworks] = useState({} as Chain[]); const [contacts, setContacts] = useState([] as Contact[]); const [ownAccounts, setOwnAccounts] = useState([] as Contact[]); const { showErrorToast } = useToast(); @@ -56,10 +56,10 @@ export const Activity = () => { const getNetworks = async () => { try { setIsLoading(true); - const networks = await Extension.getAllChains(); - setNetworks(networks); + const networks = await messageAPI.getAllChains() + setNetworks([...networks.mainnets, ...networks.testnets, ...networks.custom]); } catch (error) { - setNetworks({} as Chains); + setNetworks([]); showErrorToast(tCommon(error as string)); } finally { setIsLoading(false); @@ -69,7 +69,7 @@ export const Activity = () => { const getContacts = async () => { try { setIsLoading(true); - const { contacts, ownAccounts } = await Extension.getRegistryAddresses(); + const { contacts, ownAccounts } = await messageAPI.getRegistryAddresses(); setContacts(contacts); setOwnAccounts(ownAccounts); } catch (error) { @@ -83,7 +83,6 @@ export const Activity = () => { const getLink = (network: string, hash: string) => { const { explorer } = networks - .getAll() .find((chain) => chain.name.toLowerCase() === network.toLowerCase()) || {}; const { evm, wasm } = explorer || {}; diff --git a/src/pages/balance/components/Assets.test.tsx b/src/pages/balance/components/Assets.test.tsx index 62e3c080..4bad0b29 100644 --- a/src/pages/balance/components/Assets.test.tsx +++ b/src/pages/balance/components/Assets.test.tsx @@ -54,6 +54,23 @@ describe("Assets", () => { color: "red", }), })); + vi.mock("@src/utils/env", () => ({ + version: "1.0.0", + getWebAPI: () => ({ + tabs: { + getCurrent: () => Promise.resolve(undefined), + create: () => vi.fn(), + }, + runtime: { + getURL: vi.fn(), + connect: vi.fn().mockReturnValue({ + onMessage: { + addListener: vi.fn(), + }, + }), + }, + }), + })) }); it("should render assets", () => { renderComponent(); diff --git a/src/pages/balance/components/ChainSelector.test.tsx b/src/pages/balance/components/ChainSelector.test.tsx index 99754393..5c4914cf 100644 --- a/src/pages/balance/components/ChainSelector.test.tsx +++ b/src/pages/balance/components/ChainSelector.test.tsx @@ -17,14 +17,15 @@ const setSelectNetwork = vi.fn(); describe("ChainSelector", () => { beforeAll(() => { - vi.mock("@src/Extension", () => ({ - default: { - getAllAccounts: vi.fn().mockReturnValue(accountsMocks), + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { getSetting: vi.fn().mockReturnValue({ value: true, }), + getAllAccounts: vi.fn().mockReturnValue(accountsMocks), + }, - })); + })) vi.mock("@src/providers", () => ({ useAccountContext: vi.fn().mockReturnValue({ state: { diff --git a/src/pages/balance/components/ChainSelector.tsx b/src/pages/balance/components/ChainSelector.tsx index 054cbd37..8b2dbe9b 100644 --- a/src/pages/balance/components/ChainSelector.tsx +++ b/src/pages/balance/components/ChainSelector.tsx @@ -11,11 +11,11 @@ import { useTranslation } from "react-i18next"; import { getAccountType } from "@src/utils/account-utils"; import { useNavigate } from "react-router-dom"; import { CREATE_ACCOUNT } from "@src/routes/paths"; -import Extension from "@src/Extension"; import { AccountType } from "@src/accounts/types"; import { Chain } from "@src/storage/entities/Chains"; import { SettingKey, SettingType } from "@src/storage/entities/settings/types"; import { captureError } from "@src/utils/error-handling"; +import { messageAPI } from "@src/messageAPI/api"; export const ChainSelector = () => { const [search, setSearch] = useState(""); @@ -44,10 +44,13 @@ export const ChainSelector = () => { const getSettings = async () => { try { const showTestnets = ( - await Extension.getSetting( - SettingType.GENERAL, - SettingKey.SHOW_TESTNETS + await messageAPI.getSetting( + { + type: SettingType.GENERAL, + key: SettingKey.SHOW_TESTNETS, + } ) + )?.value as boolean; setShowTestnets(showTestnets); } catch (error) { @@ -68,10 +71,9 @@ export const ChainSelector = () => { if (!chainTypeIsSupportedBySelectedAccount) { // verify is any account support the new chain type - const accounts = await Extension.getAllAccounts( - newChainSupportedTypeAccounts - ); - + const accounts = await messageAPI.getAllAccounts({ + type: newChainSupportedTypeAccounts, + }); thereIsAccountToSupport = accounts.some((acc) => { const accountType = getAccountType(acc.type) as AccountType; return newChainSupportedTypeAccounts.includes(accountType); diff --git a/src/pages/balance/components/Settings.tsx b/src/pages/balance/components/Settings.tsx index e2b4c28f..f5c889f6 100644 --- a/src/pages/balance/components/Settings.tsx +++ b/src/pages/balance/components/Settings.tsx @@ -2,7 +2,6 @@ import { Fragment } from "react"; import { Menu, Transition } from "@headlessui/react"; import { BsChevronRight, BsGear } from "react-icons/bs"; import { useNavigate } from "react-router-dom"; -import Extension from "@src/Extension"; import { useTranslation } from "react-i18next"; import { SETTINGS_GENERAL, @@ -15,6 +14,8 @@ import { ICON_SIZE } from "@src/constants/icons"; import { RxCross2 } from "react-icons/rx"; import { useThemeContext } from "@src/providers"; import { FooterIcon } from "./FooterIcon"; +import { version } from "@src/utils/env"; + const OPTIONS = [ { @@ -89,7 +90,7 @@ export const Settings = () => {

- {Extension.version} + {version}

diff --git a/src/pages/balance/components/SignOut.tsx b/src/pages/balance/components/SignOut.tsx index d4fbc59e..8d698a76 100644 --- a/src/pages/balance/components/SignOut.tsx +++ b/src/pages/balance/components/SignOut.tsx @@ -1,14 +1,14 @@ import { useNavigate } from "react-router-dom"; import { FooterIcon } from "./FooterIcon" -import Extension from "@src/Extension"; import { SIGNIN } from "@src/routes/paths"; import { MdOutlineLock } from "react-icons/md"; +import { messageAPI } from "@src/messageAPI/api"; export const SignOut = () => { const navigate = useNavigate(); const signOut = async () => { - await Extension.signOut(); + await messageAPI.signOut() navigate(SIGNIN); }; diff --git a/src/pages/callContract/CallContract.tsx b/src/pages/callContract/CallContract.tsx index 19257e29..61da60c9 100644 --- a/src/pages/callContract/CallContract.tsx +++ b/src/pages/callContract/CallContract.tsx @@ -13,10 +13,10 @@ import { BN0, BigNumber0, PROOF_SIZE, REF_TIME } from "@src/constants/assets"; import { formatBN } from "@src/utils/assets"; import { BN } from "@polkadot/util"; import { Keyring } from "@polkadot/api"; -import Extension from "@src/Extension"; import { SubmittableExtrinsic } from "@polkadot/api/types"; import { getWebAPI } from "@src/utils/env"; import { BigNumber, Contract, ethers, utils } from "ethers"; +import { messageAPI } from "@src/messageAPI/api"; const WebAPI = getWebAPI(); @@ -149,7 +149,7 @@ export const CallContract: FC = ({ } else { if (!address.startsWith("0x")) throw new Error("invalid_address"); - const seed = await Extension.showKey(); + const seed = await messageAPI.showKey(); const wallet = new ethers.Wallet( seed as string, api as ethers.providers.JsonRpcProvider @@ -232,15 +232,21 @@ export const CallContract: FC = ({ }; useEffect(() => { - if (!api || !Extension.isAuthorized()) return; + if (!api) return; (async () => { + const isAuthorized = await messageAPI.isAuthorized(); + + if (!isAuthorized) { + return; + } + init(); })(); }, [params, api]); const send = async () => { - const seed = await Extension.showKey(); + const seed = await messageAPI.showKey(); const { id } = await WebAPI.windows.getCurrent(); starLoading(); diff --git a/src/pages/manageAssets/ManageAssets.test.tsx b/src/pages/manageAssets/ManageAssets.test.tsx index c8096d1c..76488161 100644 --- a/src/pages/manageAssets/ManageAssets.test.tsx +++ b/src/pages/manageAssets/ManageAssets.test.tsx @@ -15,6 +15,7 @@ const renderComponent = () => { }; const showErrorToast = vi.fn(); +const addAsset = vi.fn(); describe("ManageAssets", () => { beforeAll(() => { @@ -43,6 +44,12 @@ describe("ManageAssets", () => { useNavigate: () => vi.fn(), })); + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { + addAsset: () => addAsset(), + } + })) + vi.mock("@src/Extension"); vi.mock("@src/hooks", () => ({ @@ -58,12 +65,6 @@ describe("ManageAssets", () => { }); it("should fill the form and submit", async () => { - // mock extension - const _Extension = (await import("@src/Extension")).default; - - const addAsset = vi.fn(); - _Extension.addAsset = addAsset; - const { getByText, getByTestId } = renderComponent(); const addressInput = getByTestId("address"); @@ -94,11 +95,8 @@ describe("ManageAssets", () => { it("should show error on submit", async () => { // mock extension - const _Extension = (await import("@src/Extension")).default; - - const addAsset = vi.fn().mockRejectedValue(new Error("error")); - _Extension.addAsset = addAsset; - + const Default = await import("@src/messageAPI/api") + Default.messageAPI.addAsset = vi.fn().mockRejectedValue(new Error("error")); const { getByText, getByTestId } = renderComponent(); const addressInput = getByTestId("address"); diff --git a/src/pages/manageAssets/ManageAssets.tsx b/src/pages/manageAssets/ManageAssets.tsx index 4265f9f9..3d4b9760 100644 --- a/src/pages/manageAssets/ManageAssets.tsx +++ b/src/pages/manageAssets/ManageAssets.tsx @@ -3,7 +3,6 @@ import { InputErrorMessage, Button, PageWrapper } from "@src/components/common"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { useToast } from "@src/hooks"; -import Extension from "@src/Extension"; import { BALANCE } from "@src/routes/paths"; import { useAccountContext, useAssetContext, useNetworkContext } from "@src/providers"; import { number, object, string } from "yup"; @@ -12,6 +11,7 @@ import { useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import { FiChevronLeft } from "react-icons/fi"; import { decodeAddress } from "@polkadot/util-crypto"; +import { messageAPI } from "@src/messageAPI/api"; interface AssetForm { address: string; @@ -71,7 +71,10 @@ export const ManageAssets = () => { const onSubmit = handleSubmit(async (data) => { try { - await Extension.addAsset(selectedChain.name, data); + await messageAPI.addAsset({ + asset: data, + chain: selectedChain.name, + }); loadAssets({ api, selectedChain, diff --git a/src/pages/receive/Receive.test.tsx b/src/pages/receive/Receive.test.tsx index bdb599a0..b4519940 100644 --- a/src/pages/receive/Receive.test.tsx +++ b/src/pages/receive/Receive.test.tsx @@ -31,6 +31,24 @@ describe("Receive", () => { }), })); + vi.mock("@src/utils/env", () => ({ + version: "1.0.0", + getWebAPI: () => ({ + tabs: { + getCurrent: () => Promise.resolve(undefined), + create: () => vi.fn(), + }, + runtime: { + getURL: vi.fn(), + connect: vi.fn().mockReturnValue({ + onMessage: { + addListener: vi.fn(), + }, + }), + }, + }), + })) + // mock useCopyToClipboard vi.mock("@src/hooks", () => ({ useCopyToClipboard: () => ({ diff --git a/src/pages/send/Send.tsx b/src/pages/send/Send.tsx index ccc177ec..fd4edd26 100644 --- a/src/pages/send/Send.tsx +++ b/src/pages/send/Send.tsx @@ -11,16 +11,14 @@ import { AccountType } from "@src/accounts/types"; import { ConfirmTx, WasmForm, EvmForm } from "./components"; import { BALANCE } from "@src/routes/paths"; import { FiChevronLeft } from "react-icons/fi"; -import { IAsset, SendForm, Tx, TxToProcess } from "@src/types"; +import { IAsset, SendForm, Tx } from "@src/types"; import { BigNumber, Contract } from "ethers"; -import { getWebAPI } from "@src/utils/env"; import { XCM_MAPPING } from "@src/xcm/extrinsics"; import { MapResponseEVM } from "@src/xcm/interfaces"; import { isValidAddress } from "@src/utils/account-utils"; import { formatBN } from "@src/utils/assets"; import { captureError } from "@src/utils/error-handling"; - -const WebAPI = getWebAPI(); +import { messageAPI } from "@src/messageAPI/api"; export const Send = () => { const { t } = useTranslation("send"); @@ -91,27 +89,22 @@ export const Send = () => { const isXcm = getValues("isXcm"); const to = getValues("to"); - const txToSend: Partial = { - amount, - originAddress, - destinationAddress, - rpc: rpc as string, - asset: { - id: asset.id, - symbol: asset.symbol || "", - color: asset.color || "", - }, - destinationNetwork, - networkInfo: selectedChain, - originNetwork: selectedChain, - }; - try { if (tx?.type === AccountType.WASM) { - txToSend.tx = { - txHash: tx.tx.toHex(), - type: AccountType.WASM, - }; + await messageAPI.sendSubstrateTx({ + hexExtrinsic: tx.tx, + amount: amount.toString(), + asset: { + id: asset.id, + symbol: asset.symbol || "", + }, + destinationAddress, + originAddress, + destinationNetwork, + networkName: selectedChain?.name || "", + rpc: rpc as string, + }) + } else { const isNativeAsset = asset?.id === "-1"; @@ -169,22 +162,26 @@ export const Send = () => { ); } - txToSend.tx = { - txHash: _tx.hash, - type: AccountType.EVM, - }; + // async to avoid waiting for the tx to be mined + messageAPI.sendEvmTx({ + txHash: _tx.hash as string, + fee: { + gasLimit: tx?.fee.gasLimit?.toString() || "", + maxFeePerGas: tx?.fee["max fee per gas"]?.toString() || "", + maxPriorityFeePerGas: tx?.fee["max priority fee per gas"]?.toString() || "", + }, + amount: amount.toString(), + asset: { + id: asset.id, + symbol: asset.symbol || "", + }, + destinationAddress, + originAddress, + destinationNetwork, + networkName: selectedChain?.name || "", + rpc: rpc as string, + }) } - - const { id } = await WebAPI.windows.getCurrent(); - - await WebAPI.runtime.sendMessage({ - from: "popup", - origin: "kuma", - method: "process_tx", - popupId: id, - tx: txToSend, - }); - showSuccessToast(t("tx_send")); navigate(BALANCE, { state: { diff --git a/src/pages/send/components/CommonFormFields.tsx b/src/pages/send/components/CommonFormFields.tsx index 0a7a8c97..a9b29d75 100644 --- a/src/pages/send/components/CommonFormFields.tsx +++ b/src/pages/send/components/CommonFormFields.tsx @@ -1,29 +1,29 @@ -import {InputErrorMessage} from "@src/components/common"; -import {useFormContext} from "react-hook-form"; -import {useTranslation} from "react-i18next"; -import {TbChevronRight} from "react-icons/tb"; -import {NumericFormat} from "react-number-format"; -import {Destination} from "./Destination"; -import {SelectableAsset} from "./SelectableAsset"; -import {SelectableChain} from "./SelectableChain"; -import {useNetworkContext} from "@src/providers"; -import {useEffect, useState} from "react"; -import Extension from "@src/Extension"; -import {Chain} from "@src/storage/entities/Chains"; -import {XCMAlertMessage} from "./XCMAlertMessage"; +import { InputErrorMessage } from "@src/components/common"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { TbChevronRight } from "react-icons/tb"; +import { NumericFormat } from "react-number-format"; +import { Destination } from "./Destination"; +import { SelectableAsset } from "./SelectableAsset"; +import { SelectableChain } from "./SelectableChain"; +import { useNetworkContext } from "@src/providers"; +import { useEffect, useState } from "react"; +import { Chain } from "@src/storage/entities/Chains"; +import { XCMAlertMessage } from "./XCMAlertMessage"; +import { messageAPI } from "@src/messageAPI/api"; export const CommonFormFields = () => { - const {t} = useTranslation("send"); + const { t } = useTranslation("send"); const { - state: {selectedChain}, + state: { selectedChain }, } = useNetworkContext(); const { setValue, getValues, watch, - formState: {errors}, + formState: { errors }, } = useFormContext(); const to = watch("to"); @@ -34,7 +34,9 @@ export const CommonFormFields = () => { let chains = [selectedChain]; if (selectedChain.xcm) { - const xcmChains = await Extension.getXCMChains(selectedChain.name); + const xcmChains = await messageAPI.getXCMChains({ + chainName: selectedChain.name, + }); chains = [...chains, ...xcmChains]; } @@ -57,9 +59,9 @@ export const CommonFormFields = () => {

{t("from")}:

- +
- +

{t("to")}:

{

{t("destination_account")}

- + @@ -80,7 +82,7 @@ export const CommonFormFields = () => {

{t("amount")}

- +
@@ -89,7 +91,7 @@ export const CommonFormFields = () => { allowNegative={false} allowLeadingZeros={false} value={getValues("amount")} - onValueChange={({value}) => { + onValueChange={({ value }) => { setValue("amount", value || "0"); }} allowedDecimalSeparators={["%"]} @@ -103,7 +105,7 @@ export const CommonFormFields = () => { />
- +
diff --git a/src/pages/send/components/ConfirmTx.test.tsx b/src/pages/send/components/ConfirmTx.test.tsx index 259ea874..5c9a80b1 100644 --- a/src/pages/send/components/ConfirmTx.test.tsx +++ b/src/pages/send/components/ConfirmTx.test.tsx @@ -79,6 +79,23 @@ describe("ConfirmTx", () => { }, }), })); + vi.mock("@src/utils/env", () => ({ + version: "1.0.0", + getWebAPI: () => ({ + tabs: { + getCurrent: () => Promise.resolve(undefined), + create: () => vi.fn(), + }, + runtime: { + getURL: vi.fn(), + connect: vi.fn().mockReturnValue({ + onMessage: { + addListener: vi.fn(), + }, + }), + }, + }), + })) }); it("should render correctly", () => { diff --git a/src/pages/send/components/Destination.test.tsx b/src/pages/send/components/Destination.test.tsx index 39c462f5..5335a1f0 100644 --- a/src/pages/send/components/Destination.test.tsx +++ b/src/pages/send/components/Destination.test.tsx @@ -29,8 +29,8 @@ describe("Destination", () => { }), })); - vi.mock("@src/Extension", () => ({ - default: { + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { getRegistryAddresses: vi.fn().mockReturnValue({ contacts: [ { @@ -50,8 +50,8 @@ describe("Destination", () => { }, ], }), - }, - })); + } + })) vi.mock("react-hook-form", () => ({ useFormContext: () => ({ diff --git a/src/pages/send/components/Destination.tsx b/src/pages/send/components/Destination.tsx index da05c4c1..f32e9321 100644 --- a/src/pages/send/components/Destination.tsx +++ b/src/pages/send/components/Destination.tsx @@ -1,6 +1,5 @@ import { useEffect, useState, useMemo } from "react"; import { Combobox } from "@headlessui/react"; -import Extension from "@src/Extension"; import { useFormContext } from "react-hook-form"; import { useAccountContext, useNetworkContext } from "@src/providers"; import Contact from "@src/storage/entities/registry/Contact"; @@ -12,6 +11,7 @@ import { Chain } from "@src/storage/entities/Chains"; import { AccountType } from "@src/accounts/types"; import { isHex } from "@polkadot/util"; import { transformAddress } from "@src/utils/account-utils"; +import { messageAPI } from "@src/messageAPI/api"; const filterAddress = (account: Register | Contact, type: AccountType) => { @@ -49,7 +49,8 @@ export const Destination = () => { (async () => { try { const { contacts, ownAccounts, recent } = - await Extension.getRegistryAddresses(); + await messageAPI.getRegistryAddresses(); + // await Extension.getRegistryAddresses(); let _ownAccounts: Contact[] = []; diff --git a/src/pages/send/components/EvmForm.test.tsx b/src/pages/send/components/EvmForm.test.tsx index 67b6843c..1334b914 100644 --- a/src/pages/send/components/EvmForm.test.tsx +++ b/src/pages/send/components/EvmForm.test.tsx @@ -105,16 +105,15 @@ describe("EvmForm", () => { }), })); - vi.mock("@src/Extension", () => ({ - default: { + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { showKey: vi.fn().mockResolvedValue("privatekey"), isAuthorized: vi.fn().mockReturnValue(true), - }, - })); + } + })) vi.mock("ethers", async () => { const ethers = (await vi.importActual("ethers")) as any; - return { ...ethers, ethers: { @@ -211,7 +210,7 @@ describe("EvmForm", () => { expect(confirmTx).toHaveBeenCalled(); }); - it("should return error calculating gas fee", async () => { + // it("should return error calculating gas fee", async () => { - }) + // }) }); diff --git a/src/pages/send/components/EvmForm.tsx b/src/pages/send/components/EvmForm.tsx index f37772f2..eac769f9 100644 --- a/src/pages/send/components/EvmForm.tsx +++ b/src/pages/send/components/EvmForm.tsx @@ -1,7 +1,6 @@ import { FC, useEffect, useMemo, useState } from "react"; import { AccountType } from "@src/accounts/types"; import { Loading, Button, ReEnterPassword } from "@src/components/common"; -import Extension from "@src/Extension"; import { useToast } from "@src/hooks"; import { useAccountContext, @@ -23,6 +22,7 @@ import { MapResponseEVM } from "@src/xcm/interfaces"; import { ShowBalance } from "./ShowBalance"; import { isValidAddress } from "@src/utils/account-utils"; import { formatBN } from "@src/utils/assets"; +import { messageAPI } from "@src/messageAPI/api"; interface EvmFormProps { confirmTx: confirmTx; @@ -75,7 +75,7 @@ export const EvmForm: FC = ({ confirmTx }) => { const destinationIsInvalid = Boolean(errors?.destinationAccount?.message); const loadSender = async () => { - const pk = await Extension.showKey(); + const pk = await messageAPI.showKey(); const wallet = new ethers.Wallet( pk as string, @@ -86,9 +86,16 @@ export const EvmForm: FC = ({ confirmTx }) => { }; useEffect(() => { - if (Extension.isAuthorized()) { - loadSender(); - } + + (async () => { + const isAuthorized = await messageAPI.isAuthorized(); + + if (isAuthorized) { + loadSender(); + } + + })() + }, []); useEffect(() => { diff --git a/src/pages/send/components/SelectableAsset.test.tsx b/src/pages/send/components/SelectableAsset.test.tsx index 3352ecd2..99243e67 100644 --- a/src/pages/send/components/SelectableAsset.test.tsx +++ b/src/pages/send/components/SelectableAsset.test.tsx @@ -53,6 +53,24 @@ describe("SelectableAsset", () => { }), }), })); + + vi.mock("@src/utils/env", () => ({ + version: "1.0.0", + getWebAPI: () => ({ + tabs: { + getCurrent: () => Promise.resolve(undefined), + create: () => vi.fn(), + }, + runtime: { + getURL: vi.fn(), + connect: vi.fn().mockReturnValue({ + onMessage: { + addListener: vi.fn(), + }, + }), + }, + }), + })) }); it("should render", async () => { diff --git a/src/pages/send/components/WasmForm.test.tsx b/src/pages/send/components/WasmForm.test.tsx index 2eab31a6..d81f9aa5 100644 --- a/src/pages/send/components/WasmForm.test.tsx +++ b/src/pages/send/components/WasmForm.test.tsx @@ -45,6 +45,7 @@ describe("WasmForm", () => { partialFee: new BN("1000000"), }, signAsync: () => "", + toHex: () => "0x123", }), }, assets: { @@ -58,6 +59,8 @@ describe("WasmForm", () => { partialFee: new BN("1000000"), }), signAsync: () => "", + toHex: () => "0x123", + }), }, xcmPallet: { @@ -66,6 +69,8 @@ describe("WasmForm", () => { partialFee: new BN("1000000"), }), signAsync: () => "", + toHex: () => "0x123", + }), }, }, @@ -133,12 +138,12 @@ describe("WasmForm", () => { }), })); - vi.mock("@src/Extension", () => ({ - default: { + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { showKey: vi.fn().mockResolvedValue("privatekey"), isAuthorized: vi.fn().mockReturnValue(true), - }, - })); + } + })) vi.mock("@polkadot/api-contract", () => { class ContractPromise { @@ -152,6 +157,7 @@ describe("WasmForm", () => { refTime: new BN("100"), }), }, + toHex: () => "0x123", }), }, tx: { @@ -160,6 +166,7 @@ describe("WasmForm", () => { partialFee: new BN("100000000"), }, signAsync: () => "", + toHex: () => "0x123", }), }, }; diff --git a/src/pages/send/components/WasmForm.tsx b/src/pages/send/components/WasmForm.tsx index db5d975c..4ebceb54 100644 --- a/src/pages/send/components/WasmForm.tsx +++ b/src/pages/send/components/WasmForm.tsx @@ -1,5 +1,5 @@ import { FC, useEffect, useState, useMemo } from "react"; -import { Button, Loading, ReEnterPassword } from "@src/components/common"; +import { Button, Loading } from "@src/components/common"; import { useTranslation } from "react-i18next"; import { CommonFormFields } from "./CommonFormFields"; import { useFormContext } from "react-hook-form"; @@ -10,7 +10,6 @@ import { useThemeContext, } from "@src/providers"; import { ApiPromise } from "@polkadot/api"; -import Extension from "@src/Extension"; import { Keyring } from "@polkadot/keyring"; import { KeyringPair } from "@polkadot/keyring/types"; import { useToast } from "@src/hooks"; @@ -28,6 +27,7 @@ import { XCM_MAPPING } from "@src/xcm/extrinsics"; import { MapResponseXCM } from "@src/xcm/interfaces"; import { ShowBalance } from "./ShowBalance"; import { formatBN } from "@src/utils/assets"; +import { messageAPI } from "@src/messageAPI/api"; const defaultFees = { estimatedFee: new BN("0"), @@ -83,23 +83,17 @@ export const WasmForm: FC = ({ confirmTx }) => { const destinationIsInvalid = Boolean(errors?.destinationAccount?.message); const loadSender = async () => { - const seed = await Extension.showKey(); + const seed = await messageAPI.showKey() const keyring = new Keyring({ type: "sr25519" }); const sender = keyring.addFromMnemonic(seed as string); setSender(sender); }; const onSubmit = handleSubmit(async () => { - const signedTx = await (extrinsic as polkadotExtrinsic)?.signAsync( - sender as KeyringPair, - { - tip: Number(aditional.tip) * currencyUnits || "0", - } - ); - + const txHah = (extrinsic as SubmittableExtrinsic<"promise">)?.toHex() confirmTx({ type: AccountType.WASM, - tx: signedTx as polkadotExtrinsic, + tx: txHah, fee, }); }); @@ -212,14 +206,17 @@ export const WasmForm: FC = ({ confirmTx }) => { destinationAccount, bnAmount ); + } + const { partialFee } = await ( extrinsic as SubmittableExtrinsic<"promise"> ).paymentInfo(sender as KeyringPair); estimatedFee = partialFee; } + setExtrinsic(extrinsic); const amounToShow = @@ -237,9 +234,12 @@ export const WasmForm: FC = ({ confirmTx }) => { }; useEffect(() => { - if (Extension.isAuthorized()) { - loadSender(); - } + (async () => { + const isAuthorized = await messageAPI.isAuthorized(); + if (isAuthorized) { + loadSender(); + } + })() }, []); useEffect(() => { @@ -303,7 +303,7 @@ export const WasmForm: FC = ({ confirmTx }) => { return ( <> - + {/* */} diff --git a/src/pages/settings/AboutUs.tsx b/src/pages/settings/AboutUs.tsx index 72dc800a..4ccb775a 100644 --- a/src/pages/settings/AboutUs.tsx +++ b/src/pages/settings/AboutUs.tsx @@ -37,7 +37,7 @@ const links = [ url: aboutUsLinks.blockcoders, icon: ( {"blockcoders"} { const { t } = useTranslation("advanced_settings"); @@ -25,7 +25,7 @@ export const Advanced = () => { const getSettings = async () => { try { - const settings = await Extension.getAdvancedSettings(); + const settings = await messageAPI.getAdvancedSettings(); setSettings(settings); } catch (error) { setSettings([]); diff --git a/src/pages/settings/Contacts.test.tsx b/src/pages/settings/Contacts.test.tsx index fce803b5..69ace5fc 100644 --- a/src/pages/settings/Contacts.test.tsx +++ b/src/pages/settings/Contacts.test.tsx @@ -12,18 +12,35 @@ const renderComponent = () => { ); }; +const saveContact = vi.fn().mockResolvedValue([]); + + describe("Contacts", () => { beforeAll(() => { vi.mock("react-router-dom", () => ({ useNavigate: () => vi.fn(), })); - - vi.mock("@src/Extension"); + vi.mock("@src/storage/entities/registry/Contact", () => ({ + default: class Contact { + constructor() { } + } + })) + vi.mock("@src/storage/entities/BaseEntity", () => ({ + default: class BaseEntity { + constructor() { } + } + })) + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { + getContacts: vi.fn().mockReturnValue([]), + saveContact: () => saveContact(), + } + })) }); it("should render", async () => { - const _Extension = (await import("@src/Extension")).default; - _Extension.getContacts = vi.fn().mockResolvedValue([ + const Default = await import("@src/messageAPI/api") + Default.messageAPI.getContacts = vi.fn().mockResolvedValue([ { name: "alice", address: "0x123", @@ -43,8 +60,8 @@ describe("Contacts", () => { }); it("should show no contacts", async () => { - const _Extension = (await import("@src/Extension")).default; - _Extension.getContacts = vi.fn().mockResolvedValue([]); + const Default = await import("@src/messageAPI/api") + Default.messageAPI.getContacts = vi.fn().mockResolvedValue([]); const { getByText } = renderComponent(); @@ -55,10 +72,9 @@ describe("Contacts", () => { }); it("should create contact", async () => { - const saveContact = vi.fn().mockResolvedValue([]); - const _Extension = (await import("@src/Extension")).default; - _Extension.getContacts = vi.fn().mockResolvedValue([]); - _Extension.saveContact = saveContact; + const Default = await import("@src/messageAPI/api") + Default.messageAPI.getContacts = vi.fn().mockResolvedValue([]); + Default.messageAPI.saveContact = saveContact; const { getByText, getByTestId } = renderComponent(); diff --git a/src/pages/settings/Contacts.tsx b/src/pages/settings/Contacts.tsx index 4bb497b5..02abeb97 100644 --- a/src/pages/settings/Contacts.tsx +++ b/src/pages/settings/Contacts.tsx @@ -1,25 +1,25 @@ -import {useState, useEffect, useMemo} from "react"; -import {ICON_SIZE} from "@src/constants/icons"; -import {FiChevronLeft} from "react-icons/fi"; -import {useNavigate} from "react-router-dom"; +import { useState, useEffect, useMemo } from "react"; +import { ICON_SIZE } from "@src/constants/icons"; +import { FiChevronLeft } from "react-icons/fi"; +import { useNavigate } from "react-router-dom"; import Contact from "@src/storage/entities/registry/Contact"; -import {useTranslation} from "react-i18next"; -import {useToast} from "@src/hooks"; -import Extension from "@src/Extension"; +import { useTranslation } from "react-i18next"; +import { useToast } from "@src/hooks"; import { Button, InputErrorMessage, Loading, PageWrapper, } from "@src/components/common"; -import {BsTrash} from "react-icons/bs"; -import {useForm} from "react-hook-form"; -import {object, string} from "yup"; -import {yupResolver} from "@hookform/resolvers/yup"; -import {decodeAddress, encodeAddress, isAddress} from "@polkadot/util-crypto"; -import {isHex} from "@polkadot/util"; -import {captureError} from "@src/utils/error-handling"; -import {useThemeContext} from "@src/providers"; +import { BsTrash } from "react-icons/bs"; +import { useForm } from "react-hook-form"; +import { object, string } from "yup"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { decodeAddress, encodeAddress, isAddress } from "@polkadot/util-crypto"; +import { isHex } from "@polkadot/util"; +import { captureError } from "@src/utils/error-handling"; +import { useThemeContext } from "@src/providers"; +import { messageAPI } from "@src/messageAPI/api"; interface AccountForm { name: string; @@ -27,9 +27,9 @@ interface AccountForm { } export const Contacts = () => { - const {t} = useTranslation("contacts"); - const {t: tCommon} = useTranslation("common"); - const {color} = useThemeContext(); + const { t } = useTranslation("contacts"); + const { t: tCommon } = useTranslation("common"); + const { color } = useThemeContext(); const navigate = useNavigate(); const schema = object({ @@ -62,7 +62,7 @@ export const Contacts = () => { register, handleSubmit, reset, - formState: {errors}, + formState: { errors }, } = useForm({ defaultValues: { name: "", @@ -75,7 +75,7 @@ export const Contacts = () => { const [isCreateContact, setIsCreateContact] = useState(false); const [contacts, setContacts] = useState([] as Contact[]); const [search, setSearch] = useState("" as string); - const {showErrorToast} = useToast(); + const { showErrorToast } = useToast(); useEffect(() => { setIsLoading(true); @@ -84,7 +84,7 @@ export const Contacts = () => { const getContacts = async () => { try { - const contacts = await Extension.getContacts(); + const contacts = await messageAPI.getContacts(); setContacts(contacts); } catch (error) { setContacts([]); @@ -97,10 +97,11 @@ export const Contacts = () => { const saveContact = handleSubmit(async (form: AccountForm) => { try { - const {name, address} = form; - + const { name, address } = form; const contact = new Contact(name, address); - await Extension.saveContact(contact); + await messageAPI.saveContact({ + contact + }); setSearch(""); getContacts(); } catch (error) { @@ -114,7 +115,9 @@ export const Contacts = () => { const deleteContact = async (address: string) => { try { - await Extension.removeContact(address); + await messageAPI.removeContact({ + address + }); getContacts(); } catch (error) { showErrorToast(tCommon(error as string)); @@ -146,7 +149,7 @@ export const Contacts = () => { }, [contacts, search]); if (isLoading) { - return ; + return ; } return ( @@ -184,7 +187,7 @@ export const Contacts = () => { className="input-primary" {...register("name")} /> - +
@@ -198,7 +201,7 @@ export const Contacts = () => { className="input-primary" {...register("address")} /> - +
diff --git a/src/pages/settings/General.test.tsx b/src/pages/settings/General.test.tsx index 16961ab4..68491ef1 100644 --- a/src/pages/settings/General.test.tsx +++ b/src/pages/settings/General.test.tsx @@ -19,12 +19,12 @@ const getGeneralSettings = vi.fn().mockReturnValue([ ], }, { - name:SettingKey.CURRENCY, - isCurrencyArray : () => true, + name: SettingKey.CURRENCY, + isCurrencyArray: () => true, value: [ { symbol: "usd", - name:"US Dollar ($)", + name: "US Dollar ($)", }, {}, ], @@ -54,13 +54,21 @@ describe("General", () => { useNavigate: () => () => navigate(), })); - vi.mock("@src/Extension"); + vi.mock("@src/messageAPI/api", () => ({ + messageAPI: { + getGeneralSettings: () => getGeneralSettings(), + updateSetting: () => updateSetting(), + }, + })) + + vi.mock("@src/storage/entities/BaseEntity", () => ({ + default: class BaseEntity { + constructor() { } + } + })) }); it("should render", async () => { - const Extension = (await import("@src/Extension")).default; - Extension.getGeneralSettings = getGeneralSettings; - Extension.updateSetting = updateSetting; const { getByTestId } = renderComponent(); await waitFor(() => { diff --git a/src/pages/settings/General.tsx b/src/pages/settings/General.tsx index 969da80d..480f4bd6 100644 --- a/src/pages/settings/General.tsx +++ b/src/pages/settings/General.tsx @@ -4,7 +4,6 @@ import { useNavigate } from "react-router-dom"; import { PageWrapper } from "@src/components/common/PageWrapper"; import { useTranslation } from "react-i18next"; import { useEffect, useState } from "react"; -import Extension from "@src/Extension"; import { useToast } from "@src/hooks"; import Setting from "@src/storage/entities/settings/Setting"; import { @@ -19,6 +18,7 @@ import { SETTINGS_MANAGE_NETWORKS } from "@src/routes/paths"; import { Switch } from "@headlessui/react"; import { captureError } from "@src/utils/error-handling"; import { useThemeContext } from "@src/providers"; +import { messageAPI } from "@src/messageAPI/api"; export const General = () => { const { t, i18n } = useTranslation("general_settings"); @@ -39,11 +39,11 @@ export const General = () => { const getSettings = async () => { try { - const settings = await Extension.getGeneralSettings(); + const settings = await messageAPI.getGeneralSettings(); setSettings(settings); const laguagesSetting = getSettingByName(settings, SettingKey.LANGUAGES) ?.value as Language[]; - const currenciesSetting = getSettingByName(settings,SettingKey.CURRENCY)?.value as Currency[]; + const currenciesSetting = getSettingByName(settings, SettingKey.CURRENCY)?.value as Currency[]; const showTestnetsSetting = getSettingByName( settings, @@ -75,7 +75,7 @@ export const General = () => { const getSelectedCurrency = (currencies: Currency[]) => { const selectedCurrency = currencies.find( - (currency) => currency.symbol === localStorage.getItem("currency") + (currency) => currency.symbol === localStorage.getItem("currency") ); return selectedCurrency?.symbol || "usd"; } @@ -110,11 +110,12 @@ export const General = () => { if (showTestnetsSetting) { settings[settings.indexOf(showTestnetsSetting)].value = !showTestnets; setShowTestnets(!showTestnets); - await Extension.updateSetting( - SettingType.GENERAL, - SettingKey.SHOW_TESTNETS, - !showTestnets - ); + await messageAPI.updateSetting({ + type: SettingType.GENERAL, + key: SettingKey.SHOW_TESTNETS, + value: !showTestnets + }) + } } catch (error) { captureError(error); @@ -149,7 +150,7 @@ export const General = () => { onChange={(e) => saveLanguage(e.target.value)} value={selectedLanguage} > - {setting.isLanguageArray() && + { (setting.value as Language[]).map((option, index) => ( + ))} + +
); case SettingKey.MANAGE_NETWORKS: return ( @@ -208,17 +209,15 @@ export const General = () => { data-testid="show-testnets-switch" checked={showTestnets} onChange={changeShowTestnets} - className={`${ - showTestnets - ? `bg-${color}-primary` - : "bg-custom-gray-bg" - } relative inline-flex items-center h-6 rounded-full w-11 transition-colors duration-200`} + className={`${showTestnets + ? `bg-${color}-primary` + : "bg-custom-gray-bg" + } relative inline-flex items-center h-6 rounded-full w-11 transition-colors duration-200`} > {t("show_testnets")} diff --git a/src/pages/settings/ManageNetworks.tsx b/src/pages/settings/ManageNetworks.tsx index 6c795381..bb3f9ce1 100644 --- a/src/pages/settings/ManageNetworks.tsx +++ b/src/pages/settings/ManageNetworks.tsx @@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next"; import { Fragment, useEffect, useState } from "react"; import { useToast } from "@src/hooks"; import { Button, InputErrorMessage, Loading } from "@src/components/common"; -import Extension from "@src/Extension"; import Chains, { Chain } from "@src/storage/entities/Chains"; import { useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; @@ -20,6 +19,7 @@ import { AccountType } from "@src/accounts/types"; import { CHAINS } from "@src/constants/chains"; import { Listbox, Transition } from "@headlessui/react"; import { captureError } from "@src/utils/error-handling"; +import { messageAPI } from "@src/messageAPI/api"; const defaultValues: Chain = { name: "New Network", @@ -110,9 +110,9 @@ export const ManageNetworks = () => { const getNetworks = async () => { try { - const networks = await Extension.getAllChains(); + const networks = await messageAPI.getAllChains() setNetworks(networks); - const selectedNetwork = networks.getAll()[0]; + const selectedNetwork = networks.mainnets[0]; setSelectedNetwork(selectedNetwork); setValue("name", selectedNetwork.name); } catch (error) { @@ -125,8 +125,7 @@ export const ManageNetworks = () => { }; const changeNetwork = (chainName: string) => { - const network = networks - .getAll() + const network = [...networks.mainnets, ...networks.testnets, ...networks.custom] .find((network) => network.name === chainName); setSelectedNetwork(network); @@ -154,8 +153,9 @@ export const ManageNetworks = () => { const _onSubmit = handleSubmit(async (data) => { try { - await Extension.saveCustomChain(data); - getNetworks(); + await messageAPI.saveCustomChain({ + chain: data + }); setIsCreating(false); refreshNetworks(); } catch (error) { @@ -171,7 +171,9 @@ export const ManageNetworks = () => { const deleteNetwork = async () => { try { - await Extension.removeCustomChain(selectedNetwork?.name as string); + await messageAPI.removeCustomChain({ + chainName: selectedNetwork?.name as string + }); getNetworks(); const networkIsSelected = selectedChain.name === selectedNetwork?.name; @@ -258,7 +260,7 @@ export const ManageNetworks = () => { {...register("name")} > {networks && - networks?.getAll?.().map((network, index) => { + [...networks.mainnets, ...networks.testnets, ...networks.custom].map((network, index) => { return (