diff --git a/package.json b/package.json index 7f41ec36b8..759d7c14e4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "start:main": "cross-env NODE_ENV=staging pnpm r webpack:main:stage start:electron", "start:main:dev": "cross-env NODE_ENV=development pnpm r webpack:main:dev start:electron", "watch": "nodemon --watch src/main --watch src/renderer/bridge --watch src/shared --ignore src/main/resources --ext \"*\" --exec", - "webpack:renderer:dev": "webpack serve --config webpack/webpack.renderer.dev.ts", "webpack:renderer:stage": "webpack serve --config webpack/webpack.renderer.stage.ts", "webpack:renderer:prod": "webpack --config webpack/webpack.renderer.prod.ts", @@ -78,6 +77,13 @@ "@substrate/connect": "^0.7.26", "@substrate/txwrapper-orml": "^7.0.1", "@substrate/txwrapper-polkadot": "^7.0.1", + "@walletconnect/core": "^2.10.0", + "@walletconnect/modal": "^2.6.1", + "@walletconnect/sign-client": "^2.10.0", + "@walletconnect/types": "^2.10.0", + "@walletconnect/universal-provider": "^2.10.0", + "@walletconnect/utils": "^2.10.0", + "@web3modal/react": "^2.7.1", "@zxing/browser": "^0.1.3", "@zxing/library": "^0.20.0", "bignumber.js": "^9.0.2", @@ -100,6 +106,7 @@ "lottie-react": "^2.4.0", "parity-scale-codec": "^0.6.1", "patronum": "^1.19.1", + "qr-code-styling": "1.6.0-rc.1", "qrcode-generator": "^1.4.4", "raptorq": "^1.7.22", "react": "^18.2.0", @@ -141,6 +148,7 @@ "@types/jest": "^28.1.4", "@types/lodash": "^4.14.182", "@types/node": "^18.0.0", + "@types/qrcode": "^1.5.1", "@types/react": "^18.0.14", "@types/react-dom": "^18.0.5", "@types/react-lottie": "^1.2.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f95fd6332..c3cec40a6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ dependencies: version: 12.2.1(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1) '@polkadot/react-identicon': specifier: ^3.4.1 - version: 3.4.1(@babel/core@7.18.6)(@polkadot/keyring@12.2.1)(@polkadot/networks@12.4.2)(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) + version: 3.4.1(@babel/core@7.18.6)(@polkadot/keyring@12.2.1)(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1)(react-dom@18.2.0)(react@18.2.0) '@polkadot/rpc-provider': specifier: ^10.7.1 version: 10.7.1 @@ -44,6 +44,27 @@ dependencies: '@substrate/txwrapper-polkadot': specifier: ^7.0.1 version: 7.0.1(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1) + '@walletconnect/core': + specifier: ^2.10.0 + version: 2.10.0 + '@walletconnect/modal': + specifier: ^2.6.1 + version: 2.6.1(react@18.2.0) + '@walletconnect/sign-client': + specifier: ^2.10.0 + version: 2.10.0 + '@walletconnect/types': + specifier: ^2.10.0 + version: 2.10.0 + '@walletconnect/universal-provider': + specifier: ^2.10.0 + version: 2.10.0 + '@walletconnect/utils': + specifier: ^2.10.0 + version: 2.10.0 + '@web3modal/react': + specifier: ^2.7.1 + version: 2.7.1(react-dom@18.2.0)(react@18.2.0) '@zxing/browser': specifier: ^0.1.3 version: 0.1.3(@zxing/library@0.20.0) @@ -110,6 +131,9 @@ dependencies: patronum: specifier: ^1.19.1 version: 1.19.1(effector@22.8.6) + qr-code-styling: + specifier: 1.6.0-rc.1 + version: 1.6.0-rc.1 qrcode-generator: specifier: ^1.4.4 version: 1.4.4 @@ -139,7 +163,7 @@ dependencies: version: 3.0.0 styled-components: specifier: ^5.3.6 - version: 5.3.6(@babel/core@7.18.6)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) + version: 5.3.6(@babel/core@7.18.6)(react-dom@18.2.0)(react@18.2.0) swiper: specifier: ^8.3.2 version: 8.3.2 @@ -195,7 +219,7 @@ devDependencies: version: 6.5.9(@swc/core@1.3.80)(eslint@8.19.0)(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.3)(webpack-cli@5.1.4) '@storybook/react': specifier: ^6.5.9 - version: 6.5.9(@babel/core@7.18.6)(@storybook/builder-webpack5@6.5.9)(@storybook/manager-webpack5@6.5.9)(@swc/core@1.3.80)(eslint@8.19.0)(react-dom@18.2.0)(react@18.2.0)(require-from-string@2.0.2)(typescript@4.9.3)(webpack-cli@5.1.4)(webpack-dev-server@4.15.1) + version: 6.5.9(@babel/core@7.18.6)(@storybook/builder-webpack5@6.5.9)(@storybook/manager-webpack5@6.5.9)(@swc/core@1.3.80)(eslint@8.19.0)(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.3)(webpack-cli@5.1.4)(webpack-dev-server@4.15.1) '@svgr/webpack': specifier: ^6.2.1 version: 6.2.1 @@ -219,7 +243,7 @@ devDependencies: version: 13.3.0(react-dom@18.2.0)(react@18.2.0) '@testing-library/user-event': specifier: ^14.2.1 - version: 14.2.1(@testing-library/dom@9.3.1) + version: 14.2.1 '@types/jest': specifier: ^28.1.4 version: 28.1.4 @@ -229,6 +253,9 @@ devDependencies: '@types/node': specifier: ^18.0.0 version: 18.0.0 + '@types/qrcode': + specifier: ^1.5.1 + version: 1.5.1 '@types/react': specifier: ^18.0.14 version: 18.0.14 @@ -351,7 +378,7 @@ devDependencies: version: 15.0.0 jest-runner-groups: specifier: ^2.2.0 - version: 2.2.0(jest-docblock@29.6.3)(jest-runner@29.6.4) + version: 2.2.0 lint-staged: specifier: ^13.0.3 version: 13.0.3 @@ -2014,13 +2041,6 @@ packages: dependencies: regenerator-runtime: 0.14.0 - /@babel/runtime@7.22.15: - resolution: {integrity: sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - dev: true - /@babel/runtime@7.5.5: resolution: {integrity: sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==} dependencies: @@ -2828,6 +2848,16 @@ packages: resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} dev: true + /@lit-labs/ssr-dom-shim@1.1.1: + resolution: {integrity: sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==} + dev: false + + /@lit/reactive-element@1.6.3: + resolution: {integrity: sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==} + dependencies: + '@lit-labs/ssr-dom-shim': 1.1.1 + dev: false + /@malept/cross-spawn-promise@1.1.1: resolution: {integrity: sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==} engines: {node: '>= 10'} @@ -2904,6 +2934,67 @@ packages: os-filter-obj: 2.0.0 dev: true + /@motionone/animation@10.15.1: + resolution: {integrity: sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==} + dependencies: + '@motionone/easing': 10.15.1 + '@motionone/types': 10.15.1 + '@motionone/utils': 10.15.1 + tslib: 2.6.2 + dev: false + + /@motionone/dom@10.16.2: + resolution: {integrity: sha512-bnuHdNbge1FutZXv+k7xub9oPWcF0hsu8y1HTH/qg6av58YI0VufZ3ngfC7p2xhMJMnoh0LXFma2EGTgPeCkeg==} + dependencies: + '@motionone/animation': 10.15.1 + '@motionone/generators': 10.15.1 + '@motionone/types': 10.15.1 + '@motionone/utils': 10.15.1 + hey-listen: 1.0.8 + tslib: 2.6.2 + dev: false + + /@motionone/easing@10.15.1: + resolution: {integrity: sha512-6hIHBSV+ZVehf9dcKZLT7p5PEKHGhDwky2k8RKkmOvUoYP3S+dXsKupyZpqx5apjd9f+php4vXk4LuS+ADsrWw==} + dependencies: + '@motionone/utils': 10.15.1 + tslib: 2.6.2 + dev: false + + /@motionone/generators@10.15.1: + resolution: {integrity: sha512-67HLsvHJbw6cIbLA/o+gsm7h+6D4Sn7AUrB/GPxvujse1cGZ38F5H7DzoH7PhX+sjvtDnt2IhFYF2Zp1QTMKWQ==} + dependencies: + '@motionone/types': 10.15.1 + '@motionone/utils': 10.15.1 + tslib: 2.6.2 + dev: false + + /@motionone/svelte@10.16.2: + resolution: {integrity: sha512-38xsroKrfK+aHYhuQlE6eFcGy0EwrB43Q7RGjF73j/kRUTcLNu/LAaKiLLsN5lyqVzCgTBVt4TMT/ShWbTbc5Q==} + dependencies: + '@motionone/dom': 10.16.2 + tslib: 2.6.2 + dev: false + + /@motionone/types@10.15.1: + resolution: {integrity: sha512-iIUd/EgUsRZGrvW0jqdst8st7zKTzS9EsKkP+6c6n4MPZoQHwiHuVtTQLD6Kp0bsBLhNzKIBlHXponn/SDT4hA==} + dev: false + + /@motionone/utils@10.15.1: + resolution: {integrity: sha512-p0YncgU+iklvYr/Dq4NobTRdAPv9PveRDUXabPEeOjBLSO/1FNB2phNTZxOxpi1/GZwYpAoECEa0Wam+nsmhSw==} + dependencies: + '@motionone/types': 10.15.1 + hey-listen: 1.0.8 + tslib: 2.6.2 + dev: false + + /@motionone/vue@10.16.2: + resolution: {integrity: sha512-7/dEK/nWQXOkJ70bqb2KyNfSWbNvWqKKq1C8juj+0Mg/AorgD8O5wE3naddK0G+aXuNMqRuc4jlsYHHWHtIzVw==} + dependencies: + '@motionone/dom': 10.16.2 + tslib: 2.6.2 + dev: false + /@mrmlnc/readdir-enhanced@2.2.1: resolution: {integrity: sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==} engines: {node: '>=4'} @@ -3150,7 +3241,7 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/react-identicon@3.4.1(@babel/core@7.18.6)(@polkadot/keyring@12.2.1)(@polkadot/networks@12.4.2)(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0): + /@polkadot/react-identicon@3.4.1(@babel/core@7.18.6)(@polkadot/keyring@12.2.1)(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-0S8fofxKus3IORdUSSnIaiF0HKA6F+G//3+KdwiAmAlQWyfs94njQKP4IgouFSbueVyYMh4fbthQ8civ65Bkgg==} engines: {node: '>=16'} peerDependencies: @@ -3162,7 +3253,7 @@ packages: react-is: '*' dependencies: '@polkadot/keyring': 12.2.1(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1) - '@polkadot/ui-settings': 3.4.1(@polkadot/networks@12.4.2)(@polkadot/util@12.2.1) + '@polkadot/ui-settings': 3.4.1(@polkadot/util@12.2.1) '@polkadot/ui-shared': 3.4.1(@polkadot/util-crypto@12.2.1)(@polkadot/util@12.2.1) '@polkadot/util': 12.2.1 '@polkadot/util-crypto': 12.2.1(@polkadot/util@12.2.1) @@ -3171,12 +3262,10 @@ packages: react: 18.2.0 react-copy-to-clipboard: 5.1.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 - styled-components: 5.3.11(@babel/core@7.18.6)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) + styled-components: 5.3.11(@babel/core@7.18.6)(react-dom@18.2.0)(react@18.2.0) tslib: 2.6.2 transitivePeerDependencies: - '@babel/core' - - '@polkadot/networks' dev: false /@polkadot/rpc-augment@10.7.1: @@ -3296,11 +3385,10 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/ui-settings@3.4.1(@polkadot/networks@12.4.2)(@polkadot/util@12.2.1): + /@polkadot/ui-settings@3.4.1(@polkadot/util@12.2.1): resolution: {integrity: sha512-2ym8ipRl14dedExABx/+NBLxh/8W8yMukY72db+weguJBC8/AAgNAzSX4tub9IGivArSTgi2T2/zLNXEKYtA+Q==} engines: {node: '>=16'} peerDependencies: - '@polkadot/networks': '*' '@polkadot/util': '*' dependencies: '@polkadot/networks': 12.4.2 @@ -3579,6 +3667,122 @@ packages: '@sinonjs/commons': 3.0.0 dev: true + /@stablelib/aead@1.0.1: + resolution: {integrity: sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg==} + dev: false + + /@stablelib/binary@1.0.1: + resolution: {integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==} + dependencies: + '@stablelib/int': 1.0.1 + dev: false + + /@stablelib/bytes@1.0.1: + resolution: {integrity: sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ==} + dev: false + + /@stablelib/chacha20poly1305@1.0.1: + resolution: {integrity: sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA==} + dependencies: + '@stablelib/aead': 1.0.1 + '@stablelib/binary': 1.0.1 + '@stablelib/chacha': 1.0.1 + '@stablelib/constant-time': 1.0.1 + '@stablelib/poly1305': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/chacha@1.0.1: + resolution: {integrity: sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg==} + dependencies: + '@stablelib/binary': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/constant-time@1.0.1: + resolution: {integrity: sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg==} + dev: false + + /@stablelib/ed25519@1.0.3: + resolution: {integrity: sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg==} + dependencies: + '@stablelib/random': 1.0.2 + '@stablelib/sha512': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/hash@1.0.1: + resolution: {integrity: sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg==} + dev: false + + /@stablelib/hkdf@1.0.1: + resolution: {integrity: sha512-SBEHYE16ZXlHuaW5RcGk533YlBj4grMeg5TooN80W3NpcHRtLZLLXvKyX0qcRFxf+BGDobJLnwkvgEwHIDBR6g==} + dependencies: + '@stablelib/hash': 1.0.1 + '@stablelib/hmac': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/hmac@1.0.1: + resolution: {integrity: sha512-V2APD9NSnhVpV/QMYgCVMIYKiYG6LSqw1S65wxVoirhU/51ACio6D4yDVSwMzuTJXWZoVHbDdINioBwKy5kVmA==} + dependencies: + '@stablelib/constant-time': 1.0.1 + '@stablelib/hash': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/int@1.0.1: + resolution: {integrity: sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==} + dev: false + + /@stablelib/keyagreement@1.0.1: + resolution: {integrity: sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==} + dependencies: + '@stablelib/bytes': 1.0.1 + dev: false + + /@stablelib/poly1305@1.0.1: + resolution: {integrity: sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA==} + dependencies: + '@stablelib/constant-time': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/random@1.0.2: + resolution: {integrity: sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==} + dependencies: + '@stablelib/binary': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/sha256@1.0.1: + resolution: {integrity: sha512-GIIH3e6KH+91FqGV42Kcj71Uefd/QEe7Dy42sBTeqppXV95ggCcxLTk39bEr+lZfJmp+ghsR07J++ORkRELsBQ==} + dependencies: + '@stablelib/binary': 1.0.1 + '@stablelib/hash': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/sha512@1.0.1: + resolution: {integrity: sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw==} + dependencies: + '@stablelib/binary': 1.0.1 + '@stablelib/hash': 1.0.1 + '@stablelib/wipe': 1.0.1 + dev: false + + /@stablelib/wipe@1.0.1: + resolution: {integrity: sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==} + dev: false + + /@stablelib/x25519@1.0.3: + resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} + dependencies: + '@stablelib/keyagreement': 1.0.1 + '@stablelib/random': 1.0.2 + '@stablelib/wipe': 1.0.1 + dev: false + /@storybook/addon-actions@6.5.9(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wDYm3M1bN+zcYZV3Q24M03b/P8DDpvj1oSoY6VLlxDAi56h8qZB/voeIS2I6vWXOB79C5tbwljYNQO0GsufS0g==} peerDependencies: @@ -4791,7 +4995,7 @@ packages: - supports-color dev: true - /@storybook/react@6.5.9(@babel/core@7.18.6)(@storybook/builder-webpack5@6.5.9)(@storybook/manager-webpack5@6.5.9)(@swc/core@1.3.80)(eslint@8.19.0)(react-dom@18.2.0)(react@18.2.0)(require-from-string@2.0.2)(typescript@4.9.3)(webpack-cli@5.1.4)(webpack-dev-server@4.15.1): + /@storybook/react@6.5.9(@babel/core@7.18.6)(@storybook/builder-webpack5@6.5.9)(@storybook/manager-webpack5@6.5.9)(@swc/core@1.3.80)(eslint@8.19.0)(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.3)(webpack-cli@5.1.4)(webpack-dev-server@4.15.1): resolution: {integrity: sha512-Rp+QaTQAzxJhwuzJXVd49mnIBLQRlF8llTxPT2YoGHdrGkku/zl/HblQ6H2yzEf15367VyzaAv/BpLsO9Jlfxg==} engines: {node: '>=10.13.0'} hasBin: true @@ -4856,7 +5060,6 @@ packages: react-refresh: 0.11.0 read-pkg-up: 7.0.1 regenerator-runtime: 0.13.9 - require-from-string: 2.0.2 ts-dedent: 2.2.0 typescript: 4.9.3 util-deprecate: 1.0.2 @@ -5411,20 +5614,6 @@ packages: pretty-format: 27.5.1 dev: true - /@testing-library/dom@9.3.1: - resolution: {integrity: sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==} - engines: {node: '>=14'} - dependencies: - '@babel/code-frame': 7.22.13 - '@babel/runtime': 7.22.15 - '@types/aria-query': 5.0.1 - aria-query: 5.1.3 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - dev: true - /@testing-library/jest-dom@5.16.4: resolution: {integrity: sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==} engines: {node: '>=8', npm: '>=6', yarn: '>=1'} @@ -5454,13 +5643,11 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@testing-library/user-event@14.2.1(@testing-library/dom@9.3.1): + /@testing-library/user-event@14.2.1: resolution: {integrity: sha512-HOr1QiODrq+0j9lKU5i10y9TbhxMBMRMGimNx10asdmau9cb8Xb1Vyg0GvTwyIL2ziQyh2kAloOtAQFBQVuecA==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' - dependencies: - '@testing-library/dom': 9.3.1 dev: true /@tokenizer/token@0.3.0: @@ -5804,6 +5991,12 @@ packages: /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@types/qrcode@1.5.1: + resolution: {integrity: sha512-HpSN675K0PmxIDRpjMI3Mc2GiKo3dNu+X/F5SoItiaDS1lVfgC6Wac1c5lQDfKWbTJUSHWiHKzpJpBZG7k9gaA==} + dependencies: + '@types/node': 18.0.0 + dev: true + /@types/qs@6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} dev: true @@ -5908,6 +6101,10 @@ packages: resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} dev: true + /@types/trusted-types@2.0.4: + resolution: {integrity: sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==} + dev: false + /@types/uglify-js@3.17.1: resolution: {integrity: sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==} dependencies: @@ -6101,6 +6298,299 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@walletconnect/core@2.10.0: + resolution: {integrity: sha512-Z8pdorfIMueuiBXLdnf7yloiO9JIiobuxN3j0OTal+MYc4q5/2O7d+jdD1DAXbLi1taJx3x60UXT/FPVkjIqIQ==} + dependencies: + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/jsonrpc-ws-connection': 1.0.13 + '@walletconnect/keyvaluestorage': 1.0.2 + '@walletconnect/logger': 2.0.1 + '@walletconnect/relay-api': 1.0.9 + '@walletconnect/relay-auth': 1.0.4 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.10.0 + '@walletconnect/utils': 2.10.0 + events: 3.3.0 + lodash.isequal: 4.5.0 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - lokijs + - utf-8-validate + dev: false + + /@walletconnect/environment@1.0.1: + resolution: {integrity: sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==} + dependencies: + tslib: 1.14.1 + dev: false + + /@walletconnect/events@1.0.1: + resolution: {integrity: sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==} + dependencies: + keyvaluestorage-interface: 1.0.0 + tslib: 1.14.1 + dev: false + + /@walletconnect/heartbeat@1.2.1: + resolution: {integrity: sha512-yVzws616xsDLJxuG/28FqtZ5rzrTA4gUjdEMTbWB5Y8V1XHRmqq4efAxCw5ie7WjbXFSUyBHaWlMR+2/CpQC5Q==} + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/time': 1.0.2 + tslib: 1.14.1 + dev: false + + /@walletconnect/jsonrpc-http-connection@1.0.7: + resolution: {integrity: sha512-qlfh8fCfu8LOM9JRR9KE0s0wxP6ZG9/Jom8M0qsoIQeKF3Ni0FyV4V1qy/cc7nfI46SLQLSl4tgWSfLiE1swyQ==} + dependencies: + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/safe-json': 1.0.2 + cross-fetch: 3.1.8 + tslib: 1.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /@walletconnect/jsonrpc-provider@1.0.13: + resolution: {integrity: sha512-K73EpThqHnSR26gOyNEL+acEex3P7VWZe6KE12ZwKzAt2H4e5gldZHbjsu2QR9cLeJ8AXuO7kEMOIcRv1QEc7g==} + dependencies: + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/safe-json': 1.0.2 + tslib: 1.14.1 + dev: false + + /@walletconnect/jsonrpc-types@1.0.3: + resolution: {integrity: sha512-iIQ8hboBl3o5ufmJ8cuduGad0CQm3ZlsHtujv9Eu16xq89q+BG7Nh5VLxxUgmtpnrePgFkTwXirCTkwJH1v+Yw==} + dependencies: + keyvaluestorage-interface: 1.0.0 + tslib: 1.14.1 + dev: false + + /@walletconnect/jsonrpc-utils@1.0.8: + resolution: {integrity: sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw==} + dependencies: + '@walletconnect/environment': 1.0.1 + '@walletconnect/jsonrpc-types': 1.0.3 + tslib: 1.14.1 + dev: false + + /@walletconnect/jsonrpc-ws-connection@1.0.13: + resolution: {integrity: sha512-mfOM7uFH4lGtQxG+XklYuFBj6dwVvseTt5/ahOkkmpcAEgz2umuzu7fTR+h5EmjQBdrmYyEBOWADbeaFNxdySg==} + dependencies: + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/safe-json': 1.0.2 + events: 3.3.0 + tslib: 1.14.1 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@walletconnect/keyvaluestorage@1.0.2: + resolution: {integrity: sha512-U/nNG+VLWoPFdwwKx0oliT4ziKQCEoQ27L5Hhw8YOFGA2Po9A9pULUYNWhDgHkrb0gYDNt//X7wABcEWWBd3FQ==} + peerDependencies: + '@react-native-async-storage/async-storage': 1.x + lokijs: 1.x + peerDependenciesMeta: + '@react-native-async-storage/async-storage': + optional: true + lokijs: + optional: true + dependencies: + safe-json-utils: 1.1.1 + tslib: 1.14.1 + dev: false + + /@walletconnect/logger@2.0.1: + resolution: {integrity: sha512-SsTKdsgWm+oDTBeNE/zHxxr5eJfZmE9/5yp/Ku+zJtcTAjELb3DXueWkDXmE9h8uHIbJzIb5wj5lPdzyrjT6hQ==} + dependencies: + pino: 7.11.0 + tslib: 1.14.1 + dev: false + + /@walletconnect/modal-core@2.6.1(react@18.2.0): + resolution: {integrity: sha512-f2hYlJ5pwzGvjyaZ6BoGR5uiMgXzWXt6w6ktt1N8lmY6PiYp8whZgqx2hTxVWwVlsGnaIfh6UHp1hGnANx0eTQ==} + dependencies: + valtio: 1.11.0(react@18.2.0) + transitivePeerDependencies: + - react + dev: false + + /@walletconnect/modal-ui@2.6.1(react@18.2.0): + resolution: {integrity: sha512-RFUOwDAMijSK8B7W3+KoLKaa1l+KEUG0LCrtHqaB0H0cLnhEGdLR+kdTdygw+W8+yYZbkM5tXBm7MlFbcuyitA==} + dependencies: + '@walletconnect/modal-core': 2.6.1(react@18.2.0) + lit: 2.7.6 + motion: 10.16.2 + qrcode: 1.5.3 + transitivePeerDependencies: + - react + dev: false + + /@walletconnect/modal@2.6.1(react@18.2.0): + resolution: {integrity: sha512-G84tSzdPKAFk1zimgV7JzIUFT5olZUVtI3GcOk77OeLYjlMfnDT23RVRHm5EyCrjkptnvpD0wQScXePOFd2Xcw==} + dependencies: + '@walletconnect/modal-core': 2.6.1(react@18.2.0) + '@walletconnect/modal-ui': 2.6.1(react@18.2.0) + transitivePeerDependencies: + - react + dev: false + + /@walletconnect/relay-api@1.0.9: + resolution: {integrity: sha512-Q3+rylJOqRkO1D9Su0DPE3mmznbAalYapJ9qmzDgK28mYF9alcP3UwG/og5V7l7CFOqzCLi7B8BvcBUrpDj0Rg==} + dependencies: + '@walletconnect/jsonrpc-types': 1.0.3 + tslib: 1.14.1 + dev: false + + /@walletconnect/relay-auth@1.0.4: + resolution: {integrity: sha512-kKJcS6+WxYq5kshpPaxGHdwf5y98ZwbfuS4EE/NkQzqrDFm5Cj+dP8LofzWvjrrLkZq7Afy7WrQMXdLy8Sx7HQ==} + dependencies: + '@stablelib/ed25519': 1.0.3 + '@stablelib/random': 1.0.2 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + tslib: 1.14.1 + uint8arrays: 3.1.1 + dev: false + + /@walletconnect/safe-json@1.0.2: + resolution: {integrity: sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA==} + dependencies: + tslib: 1.14.1 + dev: false + + /@walletconnect/sign-client@2.10.0: + resolution: {integrity: sha512-hbDljDS53kR/It3oXD91UkcOsT6diNnW5+Zzksm0YEfwww5dop/YfNlcdnc8+jKUhWOL/YDPNQCjzsCSNlVzbw==} + dependencies: + '@walletconnect/core': 2.10.0 + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.0.1 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.10.0 + '@walletconnect/utils': 2.10.0 + events: 3.3.0 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - lokijs + - utf-8-validate + dev: false + + /@walletconnect/time@1.0.2: + resolution: {integrity: sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==} + dependencies: + tslib: 1.14.1 + dev: false + + /@walletconnect/types@2.10.0: + resolution: {integrity: sha512-kSTA/WZnbKdEbvbXSW16Ty6dOSzOZCHnGg6JH7q1MuraalD2HuNg00lVVu7QAZ/Rj1Gn9DAkrgP5Wd5a8Xq//Q==} + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/keyvaluestorage': 1.0.2 + '@walletconnect/logger': 2.0.1 + events: 3.3.0 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - lokijs + dev: false + + /@walletconnect/universal-provider@2.10.0: + resolution: {integrity: sha512-jtVWf+AeTCqBcB3lCmWkv3bvSmdRCkQdo67GNoT5y6/pvVHMxfjgrJNBOUsWQMxpREpWDpZ993X0JRjsYVsMcA==} + dependencies: + '@walletconnect/jsonrpc-http-connection': 1.0.7 + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.0.1 + '@walletconnect/sign-client': 2.10.0 + '@walletconnect/types': 2.10.0 + '@walletconnect/utils': 2.10.0 + events: 3.3.0 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - bufferutil + - encoding + - lokijs + - utf-8-validate + dev: false + + /@walletconnect/utils@2.10.0: + resolution: {integrity: sha512-9GRyEz/7CJW+G04RvrjPET5k7hOEsB9b3fF9cWDk/iDCxSWpbkU/hv/urRB36C+gvQMAZgIZYX3dHfzJWkY/2g==} + dependencies: + '@stablelib/chacha20poly1305': 1.0.1 + '@stablelib/hkdf': 1.0.1 + '@stablelib/random': 1.0.2 + '@stablelib/sha256': 1.0.1 + '@stablelib/x25519': 1.0.3 + '@walletconnect/relay-api': 1.0.9 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.10.0 + '@walletconnect/window-getters': 1.0.1 + '@walletconnect/window-metadata': 1.0.1 + detect-browser: 5.3.0 + query-string: 7.1.3 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + - lokijs + dev: false + + /@walletconnect/window-getters@1.0.1: + resolution: {integrity: sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==} + dependencies: + tslib: 1.14.1 + dev: false + + /@walletconnect/window-metadata@1.0.1: + resolution: {integrity: sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==} + dependencies: + '@walletconnect/window-getters': 1.0.1 + tslib: 1.14.1 + dev: false + + /@web3modal/core@2.7.1(react@18.2.0): + resolution: {integrity: sha512-eFWR1tK1qN4FguhaZq2VW0AVr8DC/Fox2eJnK1eTfk32hxSgUtl8wr5fVM8EoLeoapQ7Zcc0SNg/QLuLQ9t2tA==} + dependencies: + valtio: 1.11.0(react@18.2.0) + transitivePeerDependencies: + - react + dev: false + + /@web3modal/react@2.7.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-+zvP5Q7Q3ozW2hyA1NvS8giVDeZWA6PWgVhKA6656dCnOutonPUpWYuFPJahsFBYCrhLO8ApPmomgb6Dx/mh5A==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@web3modal/core': 2.7.1(react@18.2.0) + '@web3modal/ui': 2.7.1(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@web3modal/ui@2.7.1(react@18.2.0): + resolution: {integrity: sha512-JpGJbLAl/Wmuyyn+JwuwtlPD8mUW2HV/gsSxKWZoElgjbfDY4vjHo9PH2G++2HhugISfzjawp43r8lP/hWgWOQ==} + dependencies: + '@web3modal/core': 2.7.1(react@18.2.0) + lit: 2.7.6 + motion: 10.16.2 + qrcode: 1.5.3 + transitivePeerDependencies: + - react + dev: false + /@webassemblyjs/ast@1.11.6: resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} dependencies: @@ -6565,10 +7055,8 @@ packages: ajv: 6.12.6 dev: true - /ajv-formats@2.1.1(ajv@8.12.0): + /ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -6652,7 +7140,6 @@ packages: /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} @@ -6670,7 +7157,6 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} @@ -7028,6 +7514,11 @@ packages: hasBin: true dev: true + /atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + dev: false + /autoprefixer@10.4.13(postcss@8.4.18): resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} @@ -7251,7 +7742,7 @@ packages: '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.18.6) lodash: 4.17.21 picomatch: 2.3.1 - styled-components: 5.3.11(@babel/core@7.18.6)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) + styled-components: 5.3.11(@babel/core@7.18.6)(react-dom@18.2.0)(react@18.2.0) transitivePeerDependencies: - '@babel/core' dev: false @@ -7266,7 +7757,7 @@ packages: '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.18.6) lodash: 4.17.21 picomatch: 2.3.1 - styled-components: 5.3.6(@babel/core@7.18.6)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) + styled-components: 5.3.6(@babel/core@7.18.6)(react-dom@18.2.0)(react@18.2.0) transitivePeerDependencies: - '@babel/core' dev: false @@ -7894,7 +8385,6 @@ packages: /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - dev: true /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} @@ -8136,6 +8626,14 @@ packages: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false + /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: @@ -8204,14 +8702,12 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} @@ -8637,6 +9133,14 @@ packages: cross-spawn: 7.0.3 dev: true + /cross-fetch@3.1.8: + resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -9039,7 +9543,6 @@ packages: /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - dev: true /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -9048,7 +9551,6 @@ packages: /decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} - dev: true /decompress-response@3.3.0: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} @@ -9218,6 +9720,10 @@ packages: repeat-string: 1.6.1 dev: true + /detect-browser@5.3.0: + resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} + dev: false + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -9290,6 +9796,10 @@ packages: miller-rabin: 4.0.1 randombytes: 2.1.0 + /dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dev: false + /dir-compare@2.4.0: resolution: {integrity: sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==} hasBin: true @@ -9481,6 +9991,15 @@ packages: stream-shift: 1.0.1 dev: true + /duplexify@4.1.2: + resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==} + dependencies: + end-of-stream: 1.4.4 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.1 + dev: false + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true @@ -9657,7 +10176,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -9668,6 +10186,10 @@ packages: engines: {node: '>= 4'} dev: true + /encode-utf8@1.0.3: + resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} + dev: false + /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -9677,7 +10199,6 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 - dev: true /endent@2.1.0: resolution: {integrity: sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==} @@ -10294,7 +10815,6 @@ packages: /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - dev: true /evp_bytestokey@1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} @@ -10584,6 +11104,11 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fast-redact@3.3.0: + resolution: {integrity: sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==} + engines: {node: '>=6'} + dev: false + /fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -10729,6 +11254,11 @@ packages: dependencies: to-regex-range: 5.0.1 + /filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + dev: false + /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} @@ -10785,7 +11315,6 @@ packages: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - dev: true /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -11151,7 +11680,6 @@ packages: /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} @@ -11669,6 +12197,10 @@ packages: hasBin: true dev: true + /hey-listen@1.0.8: + resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} + dev: false + /highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} dev: true @@ -12322,7 +12854,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -13076,15 +13607,12 @@ packages: slash: 3.0.0 dev: true - /jest-runner-groups@2.2.0(jest-docblock@29.6.3)(jest-runner@29.6.4): + /jest-runner-groups@2.2.0: resolution: {integrity: sha512-Sp/B9ZX0CDAKa9dIkgH0sGyl2eDuScV4SVvOxqhBMxqWpsNAkmol/C58aTFmPWZj+C0ZTW1r1BSu66MTCN+voA==} engines: {node: '>= 10.14.2'} peerDependencies: jest-docblock: '>= 24' jest-runner: '>= 24' - dependencies: - jest-docblock: 29.6.3 - jest-runner: 29.6.4 dev: true /jest-runner@29.6.4: @@ -13480,6 +14008,10 @@ packages: json-buffer: 3.0.1 dev: true + /keyvaluestorage-interface@1.0.0: + resolution: {integrity: sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==} + dev: false + /kind-of@3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} engines: {node: '>=0.10.0'} @@ -13615,6 +14147,28 @@ packages: wrap-ansi: 7.0.0 dev: true + /lit-element@3.3.3: + resolution: {integrity: sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==} + dependencies: + '@lit-labs/ssr-dom-shim': 1.1.1 + '@lit/reactive-element': 1.6.3 + lit-html: 2.8.0 + dev: false + + /lit-html@2.8.0: + resolution: {integrity: sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==} + dependencies: + '@types/trusted-types': 2.0.4 + dev: false + + /lit@2.7.6: + resolution: {integrity: sha512-1amFHA7t4VaaDe+vdQejSVBklwtH9svGoG6/dZi9JhxtJBBlqY5D1RV7iLUYY0trCqQc4NfhYYZilZiVHt7Hxg==} + dependencies: + '@lit/reactive-element': 1.6.3 + lit-element: 3.3.3 + lit-html: 2.8.0 + dev: false + /load-json-file@1.1.0: resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} engines: {node: '>=0.10.0'} @@ -13679,7 +14233,6 @@ packages: engines: {node: '>=8'} dependencies: p-locate: 4.1.0 - dev: true /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -14349,6 +14902,17 @@ packages: engines: {node: '>= 8'} dev: false + /motion@10.16.2: + resolution: {integrity: sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==} + dependencies: + '@motionone/animation': 10.15.1 + '@motionone/dom': 10.16.2 + '@motionone/svelte': 10.16.2 + '@motionone/types': 10.15.1 + '@motionone/utils': 10.15.1 + '@motionone/vue': 10.16.2 + dev: false + /move-concurrently@1.0.1: resolution: {integrity: sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==} dependencies: @@ -14388,6 +14952,10 @@ packages: thunky: 1.1.0 dev: true + /multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + dev: false + /multimatch@4.0.0: resolution: {integrity: sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==} engines: {node: '>=8'} @@ -14508,7 +15076,6 @@ packages: optional: true dependencies: whatwg-url: 5.0.0 - dev: true /node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} @@ -14822,6 +15389,10 @@ packages: jwt-decode: 3.1.2 dev: true + /on-exit-leak-free@0.2.0: + resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} + dev: false + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -14953,7 +15524,6 @@ packages: engines: {node: '>=6'} dependencies: p-try: 2.2.0 - dev: true /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} @@ -14974,7 +15544,6 @@ packages: engines: {node: '>=8'} dependencies: p-limit: 2.3.0 - dev: true /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} @@ -15020,7 +15589,6 @@ packages: /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: true /package-json@6.5.0: resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} @@ -15171,7 +15739,6 @@ packages: /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -15306,6 +15873,34 @@ packages: dev: true optional: true + /pino-abstract-transport@0.5.0: + resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} + dependencies: + duplexify: 4.1.2 + split2: 4.2.0 + dev: false + + /pino-std-serializers@4.0.0: + resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==} + dev: false + + /pino@7.11.0: + resolution: {integrity: sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.3.0 + on-exit-leak-free: 0.2.0 + pino-abstract-transport: 0.5.0 + pino-std-serializers: 4.0.0 + process-warning: 1.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.1.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 2.8.0 + thread-stream: 0.15.2 + dev: false + /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -15347,6 +15942,11 @@ packages: irregular-plurals: 1.4.0 dev: true + /pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + dev: false + /pnglib@0.0.1: resolution: {integrity: sha512-95ChzOoYLOPIyVmL+Y6X+abKGXUJlvOVLkB1QQkyXl7Uczc6FElUy/x01NS7r2GX6GRezloO/ecCX9h4U9KadA==} dev: false @@ -15979,6 +16579,10 @@ packages: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true + /process-warning@1.0.0: + resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} + dev: false + /process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -16061,6 +16665,10 @@ packages: ipaddr.js: 1.9.1 dev: true + /proxy-compare@2.5.1: + resolution: {integrity: sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==} + dev: false + /prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} dev: true @@ -16134,10 +16742,27 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: true + /qr-code-styling@1.6.0-rc.1: + resolution: {integrity: sha512-ModRIiW6oUnsP18QzrRYZSc/CFKFKIdj7pUs57AEVH20ajlglRpN3HukjHk0UbNMTlKGuaYl7Gt6/O5Gg2NU2Q==} + dependencies: + qrcode-generator: 1.4.4 + dev: false + /qrcode-generator@1.4.4: resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==} dev: false + /qrcode@1.5.3: + resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + dijkstrajs: 1.0.3 + encode-utf8: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + dev: false + /qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} @@ -16157,6 +16782,16 @@ packages: engines: {node: '>=0.6'} dev: true + /query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + dev: false + /querystring-es3@0.2.1: resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} engines: {node: '>=0.4.x'} @@ -16169,6 +16804,10 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: false + /quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} @@ -16352,6 +16991,7 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true /react-merge-refs@1.1.0: resolution: {integrity: sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==} @@ -16520,6 +17160,11 @@ packages: dependencies: picomatch: 2.3.1 + /real-require@0.1.0: + resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} + engines: {node: '>= 12.13.0'} + dev: false + /realistic-structured-clone@3.0.0: resolution: {integrity: sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q==} dependencies: @@ -16782,13 +17427,16 @@ packages: /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - dev: true /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} dev: true + /require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: false + /requireindex@1.1.0: resolution: {integrity: sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==} engines: {node: '>=0.10.5'} @@ -16977,6 +17625,10 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safe-json-utils@1.1.1: + resolution: {integrity: sha512-SAJWGKDs50tAbiDXLf89PDwt9XYkWyANFWVzn4dTXl5QyI8t2o/bW5/OJl3lvc2WVU4MEpTo9Yz5NVFNsp+OJQ==} + dev: false + /safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} dependencies: @@ -16991,6 +17643,11 @@ packages: ret: 0.1.15 dev: true + /safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + dev: false + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -17076,7 +17733,7 @@ packages: dependencies: '@types/json-schema': 7.0.12 ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) + ajv-formats: 2.1.1 ajv-keywords: 5.1.0(ajv@8.12.0) dev: true @@ -17230,7 +17887,6 @@ packages: /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true /set-value@2.0.1: resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} @@ -17432,6 +18088,12 @@ packages: websocket-driver: 0.7.4 dev: true + /sonic-boom@2.8.0: + resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + /sort-keys-length@1.0.1: resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} engines: {node: '>=0.10.0'} @@ -17563,6 +18225,11 @@ packages: - supports-color dev: true + /split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + dev: false + /split-string@3.1.0: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} @@ -17576,6 +18243,11 @@ packages: readable-stream: 3.6.2 dev: true + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -17710,7 +18382,11 @@ packages: /stream-shift@1.0.1: resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} - dev: true + + /strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + dev: false /string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} @@ -17732,7 +18408,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -17822,7 +18497,6 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} @@ -17953,7 +18627,7 @@ packages: inline-style-parser: 0.1.1 dev: true - /styled-components@5.3.11(@babel/core@7.18.6)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0): + /styled-components@5.3.11(@babel/core@7.18.6)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==} engines: {node: '>=10'} peerDependencies: @@ -17971,14 +18645,13 @@ packages: hoist-non-react-statics: 3.3.2 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 shallowequal: 1.1.0 supports-color: 5.5.0 transitivePeerDependencies: - '@babel/core' dev: false - /styled-components@5.3.6(@babel/core@7.18.6)(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0): + /styled-components@5.3.6(@babel/core@7.18.6)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==} engines: {node: '>=10'} requiresBuild: true @@ -17997,7 +18670,6 @@ packages: hoist-non-react-statics: 3.3.2 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 shallowequal: 1.1.0 supports-color: 5.5.0 transitivePeerDependencies: @@ -18319,6 +18991,12 @@ packages: dependencies: any-promise: 1.3.0 + /thread-stream@0.15.2: + resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} + dependencies: + real-require: 0.1.0 + dev: false + /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: @@ -18459,7 +19137,6 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true /tr46@2.1.0: resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} @@ -18676,7 +19353,6 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -18853,6 +19529,12 @@ packages: dev: true optional: true + /uint8arrays@3.1.1: + resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} + dependencies: + multiformats: 9.9.0 + dev: false + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -19223,6 +19905,20 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /valtio@1.11.0(react@18.2.0): + resolution: {integrity: sha512-65Yd0yU5qs86b5lN1eu/nzcTgQ9/6YnD6iO+DDaDbQLn1Zv2w12Gwk43WkPlUBxk5wL/6cD5YMFf7kj6HZ1Kpg==} + engines: {node: '>=12.20.0'} + peerDependencies: + react: '>=16.8' + peerDependenciesMeta: + react: + optional: true + dependencies: + proxy-compare: 2.5.1 + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -19367,7 +20063,6 @@ packages: /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true /webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -19694,7 +20389,6 @@ packages: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: true /whatwg-url@8.7.0: resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} @@ -19724,6 +20418,10 @@ packages: is-weakset: 2.0.2 dev: true + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + dev: false + /which-typed-array@1.1.11: resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} @@ -19790,7 +20488,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} @@ -19821,6 +20518,19 @@ packages: signal-exit: 3.0.7 dev: true + /ws@7.5.9: + resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws@8.13.0: resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -19871,7 +20581,6 @@ packages: /y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} @@ -19901,6 +20610,14 @@ packages: resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==} engines: {node: '>= 14'} + /yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: false + /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -19911,6 +20628,23 @@ packages: engines: {node: '>=12'} dev: true + /yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: false + /yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} diff --git a/src/renderer/app/App.tsx b/src/renderer/app/App.tsx index 1c74c10165..5ffcedb0e1 100644 --- a/src/renderer/app/App.tsx +++ b/src/renderer/app/App.tsx @@ -10,6 +10,7 @@ import { ROUTES_CONFIG } from '@renderer/pages'; import { Paths } from '@renderer/shared/routes'; import { ConfirmDialogProvider, + StatusModalProvider, I18Provider, MatrixProvider, NetworkProvider, @@ -54,10 +55,12 @@ export const App = () => { - - {getContent()} - - + + + {getContent()} + + + diff --git a/src/renderer/app/providers/context/ConfirmContext/ConfirmContext.test.tsx b/src/renderer/app/providers/context/ConfirmContext/ConfirmContext.test.tsx index 15bfb0a3b3..2f20ecc19f 100644 --- a/src/renderer/app/providers/context/ConfirmContext/ConfirmContext.test.tsx +++ b/src/renderer/app/providers/context/ConfirmContext/ConfirmContext.test.tsx @@ -4,6 +4,14 @@ import { useToggle } from '@renderer/shared/lib/hooks'; import { ConfirmDialogProvider, useConfirmContext } from './ConfirmContext'; jest.mock('@renderer/shared/lib/hooks'); +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); describe('context/ConfirmContext', () => { afterEach(() => { diff --git a/src/renderer/app/providers/context/MatrixContext/MatrixContext.test.tsx b/src/renderer/app/providers/context/MatrixContext/MatrixContext.test.tsx index 0a89538afa..dc4c5147bd 100644 --- a/src/renderer/app/providers/context/MatrixContext/MatrixContext.test.tsx +++ b/src/renderer/app/providers/context/MatrixContext/MatrixContext.test.tsx @@ -4,6 +4,15 @@ import { Matrix } from '@renderer/shared/api/matrix'; import { ConnectionType } from '@renderer/shared/core'; import { MatrixProvider } from './MatrixContext'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + jest.mock('@renderer/shared/api/matrix', () => ({ Matrix: jest.fn().mockReturnValue({}) })); jest.mock('@renderer/entities/multisig', () => ({ diff --git a/src/renderer/app/providers/context/NetworkContext/NetworkContext.test.tsx b/src/renderer/app/providers/context/NetworkContext/NetworkContext.test.tsx index 3a83231879..338a5189fb 100644 --- a/src/renderer/app/providers/context/NetworkContext/NetworkContext.test.tsx +++ b/src/renderer/app/providers/context/NetworkContext/NetworkContext.test.tsx @@ -5,10 +5,19 @@ import { Provider } from 'effector-react'; import { useBalance } from '@renderer/entities/asset'; import { useNetwork } from '@renderer/entities/network'; import { NetworkProvider, useNetworkContext } from './NetworkContext'; -import { ConnectionStatus, ConnectionType } from '@renderer/shared/core'; +import { AccountType, ConnectionStatus, ConnectionType } from '@renderer/shared/core'; import { walletModel } from '@renderer/entities/wallet'; import { TEST_ACCOUNT_ID } from '@renderer/shared/lib/utils'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + jest.mock('@renderer/entities/network', () => ({ useNetwork: jest.fn().mockReturnValue({ connections: {}, @@ -115,7 +124,9 @@ describe('context/NetworkContext', () => { })); const scope = fork({ - values: new Map().set(walletModel.$activeAccounts, [{ name: 'Test Wallet', accountId: TEST_ACCOUNT_ID }]), + values: new Map().set(walletModel.$activeAccounts, [ + { name: 'Test Wallet', type: AccountType.BASE, accountId: TEST_ACCOUNT_ID }, + ]), }); await act(async () => { diff --git a/src/renderer/app/providers/context/NetworkContext/NetworkContext.tsx b/src/renderer/app/providers/context/NetworkContext/NetworkContext.tsx index 609cd01524..4dd465ea3d 100644 --- a/src/renderer/app/providers/context/NetworkContext/NetworkContext.tsx +++ b/src/renderer/app/providers/context/NetworkContext/NetworkContext.tsx @@ -119,9 +119,10 @@ export const NetworkProvider = ({ children }: PropsWithChildren) => { const firstPrevAcc = previousAccounts?.length && previousAccounts[0]; const firstNewAcc = activeAccounts.length && activeAccounts[0]; + if (previousAccounts?.length !== activeAccounts.length || firstPrevAcc !== firstNewAcc) { connectedConnections.forEach((chain) => { - const accountIds = getAccountIds(chain.chainId); + const accountIds = getAccountIds(chain.connection.chainId); subscribeBalanceChanges(chain, accountIds); }); } @@ -130,7 +131,7 @@ export const NetworkProvider = ({ children }: PropsWithChildren) => { const newConnections = connectedConnections.filter((c) => !previousConnectedConnections?.includes(c)); newConnections.forEach((chain) => { - const accountIds = getAccountIds(chain.chainId); + const accountIds = getAccountIds(chain.connection.chainId); subscribeBalanceChanges(chain, accountIds); }); diff --git a/src/renderer/app/providers/context/StatusContext/StatusContext.tsx b/src/renderer/app/providers/context/StatusContext/StatusContext.tsx new file mode 100644 index 0000000000..174ba50297 --- /dev/null +++ b/src/renderer/app/providers/context/StatusContext/StatusContext.tsx @@ -0,0 +1,61 @@ +import { createContext, PropsWithChildren, useCallback, useContext, useRef, useState, ReactNode } from 'react'; + +import { StatusModal } from '@renderer/shared/ui'; +import { DEFAULT_TRANSITION } from '@renderer/shared/lib/utils'; +import { useToggle } from '@renderer/shared/lib/hooks'; + +export type StatusModalProps = { + title: string; + description?: string; + content?: ReactNode; +}; + +type StatusContextProps = { + showStatus: (props: StatusModalProps) => Promise; +}; + +const StatusDialog = createContext({} as StatusContextProps); + +const defaultState = { + title: '', + description: '', +}; + +export const StatusModalProvider = ({ children }: PropsWithChildren) => { + const [isDialogOpen, toggleDialog] = useToggle(); + + const [dialogState, setDialogState] = useState(defaultState); + + const fn = useRef<() => void>(); + + const showStatus = useCallback((data: StatusModalProps): Promise => { + return new Promise((resolve) => { + setDialogState(data); + toggleDialog(); + + fn.current = () => { + toggleDialog(); + resolve(); + setTimeout(() => { + setDialogState(defaultState); + }, DEFAULT_TRANSITION); + }; + }); + }, []); + + return ( + + {children} + + fn.current?.()} + > + + ); +}; + +export const useStatusContext = () => useContext(StatusDialog); diff --git a/src/renderer/app/providers/context/StatusContext/index.ts b/src/renderer/app/providers/context/StatusContext/index.ts new file mode 100644 index 0000000000..24122db822 --- /dev/null +++ b/src/renderer/app/providers/context/StatusContext/index.ts @@ -0,0 +1 @@ +export { StatusModalProvider, useStatusContext } from './StatusContext'; diff --git a/src/renderer/app/providers/context/index.ts b/src/renderer/app/providers/context/index.ts index 928bc39f60..542ce9a25c 100644 --- a/src/renderer/app/providers/context/index.ts +++ b/src/renderer/app/providers/context/index.ts @@ -4,3 +4,4 @@ export * from './I18nContext'; export * from './MatrixContext'; export * from './MultisigChainContext'; export * from './NetworkContext'; +export * from './StatusContext'; diff --git a/src/renderer/assets/images/walletTypes/ledgerOnboarding.svg b/src/renderer/assets/images/walletTypes/ledgerOnboarding.svg new file mode 100644 index 0000000000..dcdbc13dd6 --- /dev/null +++ b/src/renderer/assets/images/walletTypes/ledgerOnboarding.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/renderer/assets/images/walletTypes/novaWalletOnboarding.svg b/src/renderer/assets/images/walletTypes/novaWalletOnboarding.svg new file mode 100644 index 0000000000..e0e82361bd --- /dev/null +++ b/src/renderer/assets/images/walletTypes/novaWalletOnboarding.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/assets/images/walletTypes/vaultOnboarding.svg b/src/renderer/assets/images/walletTypes/vaultOnboarding.svg new file mode 100644 index 0000000000..c4a3877aa6 --- /dev/null +++ b/src/renderer/assets/images/walletTypes/vaultOnboarding.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/renderer/assets/images/walletTypes/walletConnectOnboarding.svg b/src/renderer/assets/images/walletTypes/walletConnectOnboarding.svg new file mode 100644 index 0000000000..48f40d5240 --- /dev/null +++ b/src/renderer/assets/images/walletTypes/walletConnectOnboarding.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/renderer/assets/images/walletTypes/watchOnlyOnboardiing.svg b/src/renderer/assets/images/walletTypes/watchOnlyOnboardiing.svg index 5ed1d52574..3ee0fbdb6e 100644 --- a/src/renderer/assets/images/walletTypes/watchOnlyOnboardiing.svg +++ b/src/renderer/assets/images/walletTypes/watchOnlyOnboardiing.svg @@ -1,3 +1,12 @@ - - + + + + + + + + + + + diff --git a/src/renderer/assets/video/novawallet_onboarding_tutorial.mp4 b/src/renderer/assets/video/novawallet_onboarding_tutorial.mp4 new file mode 100644 index 0000000000..78be01e316 Binary files /dev/null and b/src/renderer/assets/video/novawallet_onboarding_tutorial.mp4 differ diff --git a/src/renderer/assets/video/novawallet_onboarding_tutorial.webm b/src/renderer/assets/video/novawallet_onboarding_tutorial.webm new file mode 100644 index 0000000000..13dac984e2 Binary files /dev/null and b/src/renderer/assets/video/novawallet_onboarding_tutorial.webm differ diff --git a/src/renderer/assets/video/wallet_connect_confirm.mp4 b/src/renderer/assets/video/wallet_connect_confirm.mp4 new file mode 100644 index 0000000000..5d98142e49 Binary files /dev/null and b/src/renderer/assets/video/wallet_connect_confirm.mp4 differ diff --git a/src/renderer/assets/video/wallet_connect_confirm.webm b/src/renderer/assets/video/wallet_connect_confirm.webm new file mode 100644 index 0000000000..58a6db29c2 Binary files /dev/null and b/src/renderer/assets/video/wallet_connect_confirm.webm differ diff --git a/src/renderer/components/common/QrCode/QrGeneratorContainer/QrGeneratorContainer.tsx b/src/renderer/components/common/QrCode/QrGeneratorContainer/QrGeneratorContainer.tsx index 962cb7ecab..1f3528a266 100644 --- a/src/renderer/components/common/QrCode/QrGeneratorContainer/QrGeneratorContainer.tsx +++ b/src/renderer/components/common/QrCode/QrGeneratorContainer/QrGeneratorContainer.tsx @@ -1,8 +1,6 @@ import { PropsWithChildren } from 'react'; -import cn from 'classnames'; -import { Button, CaptionText, FootnoteText, InfoLink, SmallTitleText, Icon, Shimmering } from '@renderer/shared/ui'; -import { secondsToMinutes } from '@renderer/shared/lib/utils'; +import { Button, FootnoteText, InfoLink, SmallTitleText, Icon, Shimmering, Countdown } from '@renderer/shared/ui'; import { useI18n } from '@renderer/app/providers'; import { getMetadataPortalMetadataUrl, TROUBLESHOOTING_URL } from '../common/constants'; import type { ChainId } from '@renderer/shared/core'; @@ -20,20 +18,7 @@ const QrGeneratorContainer = ({ countdown, onQrReset, chainId, children }: Props
{t('signing.scanQrTitle')} -
- {t('signing.qrCountdownTitle')} - = 60 ? 'bg-label-background-green' : 'bg-label-background-red'), - )} - > - {/* if qr not loaded yet just show zero */} - {secondsToMinutes(children ? countdown : 0)} - -
+
{children && diff --git a/src/renderer/components/common/QrCode/QrReader/QrReaderWrapper.tsx b/src/renderer/components/common/QrCode/QrReader/QrReaderWrapper.tsx index 949f351a52..74483a61e6 100644 --- a/src/renderer/components/common/QrCode/QrReader/QrReaderWrapper.tsx +++ b/src/renderer/components/common/QrCode/QrReader/QrReaderWrapper.tsx @@ -1,8 +1,8 @@ import cn from 'classnames'; import React, { useEffect, useState } from 'react'; -import { cnTw, ValidationErrors, secondsToMinutes } from '@renderer/shared/lib/utils'; -import { Shimmering, Button, CaptionText, FootnoteText, Select, SmallTitleText } from '@renderer/shared/ui'; +import { cnTw, ValidationErrors } from '@renderer/shared/lib/utils'; +import { Shimmering, Button, CaptionText, FootnoteText, Select, SmallTitleText, Countdown } from '@renderer/shared/ui'; import { DropdownOption, DropdownResult } from '@renderer/shared/ui/Dropdowns/common/types'; import { useI18n } from '@renderer/app/providers'; import SignatureReaderError from './SignatureReaderError'; @@ -129,20 +129,7 @@ const QrReaderWrapper = ({ className, onResult, countdown, validationError, isMu {t('signing.scanQrTitle')} - {/* countdown */} -
- {t('signing.qrCountdownTitle')} - = 60 ? 'bg-label-background-green' : 'bg-label-background-red'), - )} - > - {secondsToMinutes(countdown)} - -
+ {/* scanning frame */}
diff --git a/src/renderer/entities/chain/ui/ChainIcon/ChainIcon.test.tsx b/src/renderer/entities/chain/ui/ChainIcon/ChainIcon.test.tsx index 4e23e762f7..8f43aea409 100644 --- a/src/renderer/entities/chain/ui/ChainIcon/ChainIcon.test.tsx +++ b/src/renderer/entities/chain/ui/ChainIcon/ChainIcon.test.tsx @@ -3,6 +3,15 @@ import { act, render, screen } from '@testing-library/react'; import { ChainIcon } from './ChainIcon'; import { TEST_CHAIN_ICON } from '@renderer/shared/lib/utils'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/ChainIcon', () => { test('should render component', async () => { await act(async () => { diff --git a/src/renderer/entities/chain/ui/ChainTitle/ChainTitle.test.tsx b/src/renderer/entities/chain/ui/ChainTitle/ChainTitle.test.tsx index e9c665b59d..c5dca6f8f6 100644 --- a/src/renderer/entities/chain/ui/ChainTitle/ChainTitle.test.tsx +++ b/src/renderer/entities/chain/ui/ChainTitle/ChainTitle.test.tsx @@ -3,6 +3,15 @@ import { act, render, screen } from '@testing-library/react'; import { ChainTitle } from './ChainTitle'; import { TEST_CHAIN_ID } from '@renderer/shared/lib/utils'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/ChainTitle', () => { test('should render component', async () => { await act(async () => { diff --git a/src/renderer/entities/chain/ui/XcmChains/XcmChains.test.tsx b/src/renderer/entities/chain/ui/XcmChains/XcmChains.test.tsx index ec13b799dc..6e791e73f0 100644 --- a/src/renderer/entities/chain/ui/XcmChains/XcmChains.test.tsx +++ b/src/renderer/entities/chain/ui/XcmChains/XcmChains.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import { XcmChains } from './XcmChains'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + jest.mock('../ChainTitle/ChainTitle', () => ({ ChainTitle: ({ chainId }: any) => {chainId}, })); diff --git a/src/renderer/entities/operation/ui/SignButton.tsx b/src/renderer/entities/operation/ui/SignButton.tsx new file mode 100644 index 0000000000..868ab28ea6 --- /dev/null +++ b/src/renderer/entities/operation/ui/SignButton.tsx @@ -0,0 +1,48 @@ +import { useI18n } from '@renderer/app/providers'; +import { WalletType } from '@renderer/shared/core'; +import { Button, Icon } from '@renderer/shared/ui'; +import { IconNames } from '@renderer/shared/ui/Icon/data'; + +type Props = { + type: WalletType; + disabled?: boolean; + onClick?: () => void; + className?: string; +}; + +const WalletIcon: Record = { + [WalletType.POLKADOT_VAULT]: 'vault', + [WalletType.MULTISIG]: 'vault', + [WalletType.WATCH_ONLY]: 'watchOnly', + [WalletType.WALLET_CONNECT]: 'walletConnect', + [WalletType.NOVA_WALLET]: 'novaWallet', + // legacy + [WalletType.MULTISHARD_PARITY_SIGNER]: 'vault', + [WalletType.SINGLE_PARITY_SIGNER]: 'vault', +}; + +const WalletText: Record = { + [WalletType.POLKADOT_VAULT]: 'operation.polkadotVault', + [WalletType.MULTISIG]: 'operation.polkadotVault', + [WalletType.WATCH_ONLY]: 'operation.sign.watchOnly', + [WalletType.WALLET_CONNECT]: 'operation.sign.walletConnect', + [WalletType.NOVA_WALLET]: 'operation.sign.novaWallet', + // legacy + [WalletType.MULTISHARD_PARITY_SIGNER]: 'operation.sign.polkadotVault', + [WalletType.SINGLE_PARITY_SIGNER]: 'operation.sign.polkadotVault', +}; + +export const SignButton = ({ disabled, type, onClick, className }: Props) => { + const { t } = useI18n(); + + return ( + + ); +}; diff --git a/src/renderer/entities/transaction/ui/OperationResult/OperationResult.test.tsx b/src/renderer/entities/transaction/ui/OperationResult/OperationResult.test.tsx index a1aa367d80..86dafa4ea6 100644 --- a/src/renderer/entities/transaction/ui/OperationResult/OperationResult.test.tsx +++ b/src/renderer/entities/transaction/ui/OperationResult/OperationResult.test.tsx @@ -3,6 +3,15 @@ import noop from 'lodash/noop'; import { OperationResult } from './OperationResult'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + jest.mock('@renderer/shared/ui/Animation/Animation', () => ({ Animation: () => animation, })); diff --git a/src/renderer/entities/transaction/ui/OperationResult/OperationResult.tsx b/src/renderer/entities/transaction/ui/OperationResult/OperationResult.tsx index d6925d878e..afa4e8e753 100644 --- a/src/renderer/entities/transaction/ui/OperationResult/OperationResult.tsx +++ b/src/renderer/entities/transaction/ui/OperationResult/OperationResult.tsx @@ -1,12 +1,9 @@ -import { Fragment, PropsWithChildren, useEffect, useState } from 'react'; -import { Dialog, Transition } from '@headlessui/react'; +import { PropsWithChildren } from 'react'; -import { ModalBackdrop, ModalTransition } from '@renderer/shared/ui/Modals/common'; -import { FootnoteText, SmallTitleText } from '@renderer/shared/ui'; +import { StatusModal } from '@renderer/shared/ui'; import { Animation } from '@renderer/shared/ui/Animation/Animation'; import { VariantAnimationProps } from './common/constants'; import { Variant } from './common/types'; -import Animations from '@renderer/shared/ui/Animation/Data'; type Props = { title: string; @@ -24,38 +21,15 @@ export const OperationResult = ({ children, onClose, }: PropsWithChildren) => { - const [animation, setAnimation] = useState(); - - useEffect(() => { - if (isOpen) { - // using same animation repeatedly without deep clone lead to memory leak - // https://github.com/airbnb/lottie-web/issues/1159 - setAnimation(JSON.parse(JSON.stringify(Animations[variant]))); - } - }, [isOpen, variant]); - return ( - - - - -
- - - {animation && } - - {title} - - {description && ( - - {description} - - )} -
{children}
-
-
-
-
-
+ } + title={title} + description={description} + isOpen={isOpen} + onClose={onClose} + > + {children} + ); }; diff --git a/src/renderer/entities/wallet/lib/__tests__/model-utils.test.ts b/src/renderer/entities/wallet/lib/__tests__/model-utils.test.ts index fb09cf4a13..18d3a8ffb9 100644 --- a/src/renderer/entities/wallet/lib/__tests__/model-utils.test.ts +++ b/src/renderer/entities/wallet/lib/__tests__/model-utils.test.ts @@ -2,6 +2,15 @@ import { modelUtils } from '../model-utils'; import { AccountType, ChainType, CryptoType, KeyType, BaseAccount, ChainAccount } from '@renderer/shared/core'; import { TEST_ACCOUNT_ID, TEST_CHAIN_ID } from '@renderer/shared/lib/utils'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + const accounts = [ { name: 'My base account', diff --git a/src/renderer/entities/wallet/lib/account-utils.ts b/src/renderer/entities/wallet/lib/account-utils.ts index 463d2ac8ec..cad00534cb 100644 --- a/src/renderer/entities/wallet/lib/account-utils.ts +++ b/src/renderer/entities/wallet/lib/account-utils.ts @@ -2,13 +2,22 @@ import { u8aToHex } from '@polkadot/util'; import { createKeyMulti } from '@polkadot/util-crypto'; import { AccountType, ChainId } from '@renderer/shared/core'; -import type { AccountId, Threshold, MultisigAccount, Account, BaseAccount, ChainAccount } from '@renderer/shared/core'; +import type { + AccountId, + Threshold, + MultisigAccount, + Account, + BaseAccount, + ChainAccount, + WalletConnectAccount, +} from '@renderer/shared/core'; export const accountUtils = { isBaseAccount, isChainAccount, isMultisigAccount, isChainIdMatch, + isWalletConnectAccount, getMultisigAccountId, }; @@ -24,10 +33,18 @@ function isChainAccount(account: Pick): account is ChainAccount return account.type === AccountType.CHAIN; } -function isChainIdMatch(account: Pick, chainId: ChainId): boolean { - return !isChainAccount(account) || account.chainId === chainId; +function isWalletConnectAccount(account: Pick): account is WalletConnectAccount { + return account.type === AccountType.WALLET_CONNECT; } +function isChainIdMatch(account: Pick, chainId: ChainId): boolean { + if (isBaseAccount(account) || isMultisigAccount(account)) return true; + + const chainAccountMatch = isChainAccount(account) && account.chainId === chainId; + const walletConnectAccountMatch = isWalletConnectAccount(account) && account.chainId === chainId; + + return chainAccountMatch || walletConnectAccountMatch; +} function isMultisigAccount(account: Pick): account is MultisigAccount { return account.type === AccountType.MULTISIG; } diff --git a/src/renderer/entities/wallet/lib/wallet-utils.ts b/src/renderer/entities/wallet/lib/wallet-utils.ts index 5fb1145f72..31639ef8a3 100644 --- a/src/renderer/entities/wallet/lib/wallet-utils.ts +++ b/src/renderer/entities/wallet/lib/wallet-utils.ts @@ -7,6 +7,8 @@ export const walletUtils = { isSingleShard, isMultisig, isWatchOnly, + isNovaWallet, + isWalletConnect, }; function isPolkadotVault(wallet?: Wallet | null): boolean { @@ -35,3 +37,11 @@ function isMultisig(wallet?: Wallet | null): boolean { function isWatchOnly(wallet?: Wallet | null): boolean { return wallet?.type === WalletType.WATCH_ONLY; } + +function isNovaWallet(wallet?: Wallet | null): boolean { + return wallet?.type === WalletType.NOVA_WALLET; +} + +function isWalletConnect(wallet?: Wallet | null): boolean { + return wallet?.type === WalletType.WALLET_CONNECT; +} diff --git a/src/renderer/entities/wallet/model/__tests__/wallet-model.test.ts b/src/renderer/entities/wallet/model/__tests__/wallet-model.test.ts index d31e8da218..216d11f213 100644 --- a/src/renderer/entities/wallet/model/__tests__/wallet-model.test.ts +++ b/src/renderer/entities/wallet/model/__tests__/wallet-model.test.ts @@ -5,6 +5,15 @@ import { walletMock } from './mocks/wallet-mock'; import { kernelModel } from '@renderer/shared/core'; import { storageService } from '@renderer/shared/api/storage'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('entities/wallet/model/wallet-model', () => { afterEach(() => { jest.clearAllMocks(); diff --git a/src/renderer/entities/wallet/model/wallet-model.ts b/src/renderer/entities/wallet/model/wallet-model.ts index d852213dff..a0e8058f0c 100644 --- a/src/renderer/entities/wallet/model/wallet-model.ts +++ b/src/renderer/entities/wallet/model/wallet-model.ts @@ -2,7 +2,7 @@ import { createStore, createEvent, forward, createEffect, sample, combine } from import { spread } from 'patronum'; import type { Wallet, NoID, Account, BaseAccount, ChainAccount, MultisigAccount } from '@renderer/shared/core'; -import { kernelModel } from '@renderer/shared/core'; +import { kernelModel, WalletConnectAccount } from '@renderer/shared/core'; import { storageService } from '@renderer/shared/api/storage'; import { modelUtils } from '../lib/model-utils'; @@ -29,6 +29,8 @@ const watchOnlyCreated = createEvent>(); const multishardCreated = createEvent>(); const singleshardCreated = createEvent>(); const multisigCreated = createEvent>(); +const walletConnectCreated = createEvent>(); + const walletSelected = createEvent(); const multisigAccountUpdated = createEvent(); @@ -46,7 +48,7 @@ type CreateResult = { }; const walletCreatedFx = createEffect( - async ({ wallet, accounts }: CreateParams): Promise => { + async ({ wallet, accounts }: CreateParams): Promise => { const dbWallet = await storageService.wallets.create({ ...wallet, isActive: false }); if (!dbWallet) return undefined; @@ -150,6 +152,11 @@ sample({ target: $wallets, }); +forward({ + from: walletConnectCreated, + to: walletCreatedFx, +}); + forward({ from: [watchOnlyCreated, multisigCreated, singleshardCreated], to: walletCreatedFx, @@ -201,6 +208,7 @@ export const walletModel = { multishardCreated, singleshardCreated, multisigCreated, + walletConnectCreated, walletSelected, multisigAccountUpdated, }, diff --git a/src/renderer/entities/wallet/ui/AccountAddress/AccountAddress.test.tsx b/src/renderer/entities/wallet/ui/AccountAddress/AccountAddress.test.tsx index 78a24a0c8f..13b1a84ef4 100644 --- a/src/renderer/entities/wallet/ui/AccountAddress/AccountAddress.test.tsx +++ b/src/renderer/entities/wallet/ui/AccountAddress/AccountAddress.test.tsx @@ -3,6 +3,15 @@ import { render, screen } from '@testing-library/react'; import { AccountAddress } from './AccountAddress'; import { TEST_ACCOUNT_ID, TEST_ADDRESS } from '@renderer/shared/lib/utils'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/AccountAddress', () => { test('should render component', () => { render(); diff --git a/src/renderer/entities/wallet/ui/AddressWithName/AddressWithName.test.tsx b/src/renderer/entities/wallet/ui/AddressWithName/AddressWithName.test.tsx index 27fb0d9c18..85ea5aa343 100644 --- a/src/renderer/entities/wallet/ui/AddressWithName/AddressWithName.test.tsx +++ b/src/renderer/entities/wallet/ui/AddressWithName/AddressWithName.test.tsx @@ -3,6 +3,15 @@ import { render, screen } from '@testing-library/react'; import { AddressWithName } from './AddressWithName'; import { TEST_ACCOUNT_ID, TEST_ADDRESS } from '@renderer/shared/lib/utils'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Address', () => { test('should render component', () => { render(); diff --git a/src/renderer/entities/wallet/ui/MultiAccountsList/MultiAccountsList.tsx b/src/renderer/entities/wallet/ui/MultiAccountsList/MultiAccountsList.tsx new file mode 100644 index 0000000000..94d2e00b34 --- /dev/null +++ b/src/renderer/entities/wallet/ui/MultiAccountsList/MultiAccountsList.tsx @@ -0,0 +1,52 @@ +import { cnTw } from '@renderer/shared/lib/utils'; +import { ChainTitle } from '@renderer/entities/chain'; +import { useI18n } from '@renderer/app/providers'; +import { FootnoteText } from '@renderer/shared/ui'; +import { AccountId, Chain } from '@renderer/shared/core'; +import { AddressWithExplorers } from '@renderer/entities/wallet'; + +type Props = { + accounts: { + chain: Chain; + accountId: AccountId; + }[]; + className?: string; +}; + +export const MultiAccountsList = ({ accounts, className }: Props) => { + const { t } = useI18n(); + + return ( + <> +
+ + {t('accountList.networksColumn', { chains: accounts.length })} + + {t('accountList.addressColumn')} +
+ +
    + {accounts.map(({ chain, accountId }) => { + const { chainId, addressPrefix, explorers } = chain; + + return ( +
  • + + +
    + +
    +
  • + ); + })} +
+ + ); +}; diff --git a/src/renderer/entities/wallet/ui/index.ts b/src/renderer/entities/wallet/ui/index.ts index 687f311154..4a701e4307 100644 --- a/src/renderer/entities/wallet/ui/index.ts +++ b/src/renderer/entities/wallet/ui/index.ts @@ -3,4 +3,5 @@ export { AccountsList } from './AccountsList/AccountsList'; export { AddressWithName } from './AddressWithName/AddressWithName'; export { AccountAddress, getAddress } from './AccountAddress/AccountAddress'; export { AddressWithTwoLines } from './AddressWithTwoLines/AddressWithTwoLines'; +export { MultiAccountsList } from './MultiAccountsList/MultiAccountsList'; export type { AccountAddressProps } from './AccountAddress/AccountAddress'; diff --git a/src/renderer/entities/walletConnect/index.ts b/src/renderer/entities/walletConnect/index.ts new file mode 100644 index 0000000000..a715b06e3b --- /dev/null +++ b/src/renderer/entities/walletConnect/index.ts @@ -0,0 +1,2 @@ +export * from './lib'; +export * from './model/wallet-connect-model'; diff --git a/src/renderer/entities/walletConnect/lib/common/constants.ts b/src/renderer/entities/walletConnect/lib/common/constants.ts new file mode 100644 index 0000000000..f2d5272ea1 --- /dev/null +++ b/src/renderer/entities/walletConnect/lib/common/constants.ts @@ -0,0 +1,51 @@ +export const DEFAULT_PROJECT_ID = 'af50115ecc7e992a0ef4a577daf5c1c8'; +export const DEFAULT_RELAY_URL = 'wss://relay.walletconnect.com'; + +export const DEFAULT_LOGGER = 'debug'; + +export const DEFAULT_APP_METADATA = { + name: 'Nova Spektr', //dApp name + description: 'Full-spectrum Polkadot Desktop Wallet', //dApp description + url: 'https://novaspektr.io', //dApp url + icons: ['https://drive.google.com/uc?id=1oud8FHw3PcldUgHVeX5OjCg8XANhGO5s'], //dApp logo url + verifyUrl: 'https://verify.walletconnect.com', +}; + +/** + * POLKADOT + */ +export enum DEFAULT_POLKADOT_METHODS { + POLKADOT_SIGN_TRANSACTION = 'polkadot_signTransaction', +} + +export enum DEFAULT_POLKADOT_EVENTS { + CHAIN_CHANGED = 'chainChanged', + ACCOUNTS_CHANGED = 'accountsChanged', +} + +type RelayerType = { + value: string | undefined; + label: string; +}; + +export const REGIONALIZED_RELAYER_ENDPOINTS: RelayerType[] = [ + { + value: DEFAULT_RELAY_URL, + label: 'Default', + }, + + { + value: 'wss://us-east-1.relay.walletconnect.com', + label: 'US', + }, + { + value: 'wss://eu-central-1.relay.walletconnect.com', + label: 'EU', + }, + { + value: 'wss://ap-southeast-1.relay.walletconnect.com', + label: 'Asia Pacific', + }, +]; + +export const WALLETCONNECT_CLIENT_ID = 'WALLETCONNECT_CLIENT_ID'; diff --git a/src/renderer/entities/walletConnect/lib/common/types.ts b/src/renderer/entities/walletConnect/lib/common/types.ts new file mode 100644 index 0000000000..02bba98a5a --- /dev/null +++ b/src/renderer/entities/walletConnect/lib/common/types.ts @@ -0,0 +1,19 @@ +import Client from '@walletconnect/sign-client'; +import { SessionTypes } from '@walletconnect/types'; + +export type InitConnectProps = { + client: Client; + chains: string[]; + pairing?: any; +}; + +export type ConnectProps = { + client: Client; + approval: () => Promise; + onConnect?: () => void; +}; + +export type DisconnectProps = { + client: Client; + session: SessionTypes.Struct; +}; diff --git a/src/renderer/entities/walletConnect/lib/common/utils.ts b/src/renderer/entities/walletConnect/lib/common/utils.ts new file mode 100644 index 0000000000..5e9e1e20db --- /dev/null +++ b/src/renderer/entities/walletConnect/lib/common/utils.ts @@ -0,0 +1,5 @@ +import { Chain } from '@renderer/shared/core'; + +export const getWalletConnectChains = (chains: Chain[]) => { + return chains.map((c) => `polkadot:${c.chainId.slice(2, 34)}`); +}; diff --git a/src/renderer/entities/walletConnect/lib/index.ts b/src/renderer/entities/walletConnect/lib/index.ts new file mode 100644 index 0000000000..5bbc83f7e1 --- /dev/null +++ b/src/renderer/entities/walletConnect/lib/index.ts @@ -0,0 +1,3 @@ +export * from './common/utils'; +export * from './common/types'; +export * from './common/constants'; diff --git a/src/renderer/entities/walletConnect/model/wallet-connect-model.ts b/src/renderer/entities/walletConnect/model/wallet-connect-model.ts new file mode 100644 index 0000000000..700c4085f6 --- /dev/null +++ b/src/renderer/entities/walletConnect/model/wallet-connect-model.ts @@ -0,0 +1,323 @@ +import Client from '@walletconnect/sign-client'; +import { PairingTypes, SessionTypes } from '@walletconnect/types'; +import { createEffect, createEvent, createStore, forward, sample, scopeBind } from 'effector'; +import { getSdkError } from '@walletconnect/utils'; +import keyBy from 'lodash/keyBy'; + +import { nonNullable } from '@renderer/shared/lib/utils'; +import { + ConnectProps, + DEFAULT_APP_METADATA, + DEFAULT_LOGGER, + DEFAULT_POLKADOT_EVENTS, + DEFAULT_POLKADOT_METHODS, + DEFAULT_PROJECT_ID, + DEFAULT_RELAY_URL, + DisconnectProps, + InitConnectProps, + WALLETCONNECT_CLIENT_ID, +} from '../lib'; +import { Account, kernelModel } from '@renderer/shared/core'; +import { localStorageService } from '@renderer/shared/api/local-storage'; +import { walletModel } from '@renderer/entities/wallet/model/wallet-model'; +import { storageService } from '@renderer/shared/api/storage'; + +type InitConnectResult = { + uri: string | undefined; + approval: () => Promise; +}; + +type ConnectResult = { + pairings: PairingTypes.Struct[]; + session: SessionTypes.Struct; +}; + +type SessionTopicUpdateProps = { + activeAccounts: Account[]; + sessionTopic: string; +}; + +const connect = createEvent>(); +const disconnect = createEvent(); +const reset = createEvent(); +const sessionUpdated = createEvent(); +const connected = createEvent(); +const rejectConnection = createEvent(); +const sessionTopicUpdated = createEvent(); + +const $client = createStore(null).reset(reset); +const $session = createStore(null).reset(reset); +const $uri = createStore('').reset(reset); +const $accounts = createStore([]).reset(reset); +const $pairings = createStore([]).reset(reset); + +const subscribeToEventsFx = createEffect((client: Client) => { + const bindedSessionUpdated = scopeBind(sessionUpdated); + const bindedReset = scopeBind(reset); + + client.on('session_update', ({ topic, params }) => { + console.log('WC EVENT', 'session_update', { topic, params }); + const { namespaces } = params; + const _session = client.session.get(topic); + const updatedSession = { ..._session, namespaces }; + + bindedSessionUpdated(updatedSession); + }); + + client.on('session_ping', (args) => { + console.log('WC EVENT', 'session_ping', args); + }); + + client.on('session_event', (args) => { + console.log('WC EVENT', 'session_event', args); + }); + + client.on('session_delete', () => { + console.log('WC EVENT', 'session_delete'); + bindedReset(); + }); +}); + +const checkPersistedStateFx = createEffect(async (client: Client) => { + if (!client) return; + + const pairings = client.pairing.getAll({ active: true }); + + // Set pairings + console.log('RESTORED PAIRINGS: ', pairings); + + if (client.session.length) { + const lastKeyIndex = client.session.keys.length - 1; + const session = client.session.get(client.session.keys[lastKeyIndex]); + console.log('RESTORED SESSION:', session); + + sessionUpdated(session); + } +}); + +const logClientIdFx = createEffect(async (client: Client) => { + if (!client) return; + + try { + const clientId = await client.core.crypto.getClientId(); + console.log('WalletConnect ClientID: ', clientId); + localStorageService.saveToStorage(WALLETCONNECT_CLIENT_ID, clientId); + } catch (error) { + console.error('Failed to set WalletConnect clientId in localStorage: ', error); + } +}); + +const sessionTopicUpdatedFx = createEffect( + async ({ activeAccounts, sessionTopic }: SessionTopicUpdateProps): Promise => { + const updatedAccounts = activeAccounts.map(({ signingExtras, ...rest }) => { + const newSigningExtras = { ...signingExtras, sessionTopic }; + + return { ...rest, signingExtras: newSigningExtras } as Account; + }); + const updated = await storageService.accounts.updateAll(updatedAccounts); + + return updated && updatedAccounts; + }, +); + +const createClientFx = createEffect(async (): Promise => { + try { + return Client.init({ + logger: DEFAULT_LOGGER, + relayUrl: DEFAULT_RELAY_URL, + projectId: DEFAULT_PROJECT_ID, + metadata: DEFAULT_APP_METADATA, + }); + } catch (e) { + console.log(`Failed to create new Client`, e); + } +}); + +forward({ + from: kernelModel.events.appStarted, + to: createClientFx, +}); + +sample({ + clock: createClientFx.doneData, + filter: (client): client is Client => client !== null, + target: [subscribeToEventsFx, checkPersistedStateFx, logClientIdFx], +}); + +const initConnectFx = createEffect( + async ({ client, chains, pairing }: InitConnectProps): Promise => { + try { + const optionalNamespaces = { + polkadot: { + methods: [DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION], + chains, + events: [DEFAULT_POLKADOT_EVENTS.CHAIN_CHANGED, DEFAULT_POLKADOT_EVENTS.ACCOUNTS_CHANGED], + }, + }; + + const { uri, approval } = await client.connect({ + pairingTopic: pairing?.topic, + optionalNamespaces, + }); + + return { + uri, + approval, + }; + } catch (e) { + console.log(`Failed to init connection`, e); + } + }, +); + +const connectFx = createEffect(async ({ client, approval }: ConnectProps): Promise => { + try { + const session = await approval(); + console.log('Established session:', session); + + return { + pairings: client.pairing.getAll({ active: true }), + session: session as SessionTypes.Struct, + }; + } catch (e: any) { + rejectConnection(e); + } +}); + +const disconnectFx = createEffect(async ({ client, session }: DisconnectProps) => { + try { + await client.disconnect({ + topic: session.topic, + reason: getSdkError('USER_DISCONNECTED'), + }); + } catch (error) { + return; + } +}); + +forward({ + from: sessionUpdated, + to: $session, +}); + +sample({ + clock: createClientFx.doneData, + filter: (client) => nonNullable(client), + fn: (client) => client!, + target: $client, +}); + +sample({ + clock: connect, + source: $client, + filter: (client, props) => client !== null && props.chains.length > 0, + fn: (client, props) => ({ + client: client!, + ...props, + }), + target: initConnectFx, +}); + +sample({ + clock: initConnectFx.doneData, + source: $client, + filter: (client, initData) => client !== null && initData?.approval !== null, + fn: ($client, initData) => ({ + client: $client!, + approval: initData?.approval!, + }), + target: connectFx, +}); + +sample({ + clock: initConnectFx.doneData, + fn: (data) => data?.uri || '', + target: $uri, +}); + +sample({ + clock: connectFx.doneData, + filter: (props) => nonNullable(props), + fn: (props) => { + return Object.values(props!.session.namespaces) + .map((namespace) => namespace.accounts) + .flat(); + }, + target: $accounts, +}); + +sample({ + clock: connectFx.doneData, + filter: (props) => nonNullable(props), + fn: (props) => props!.session, + target: $session, +}); + +sample({ + clock: connectFx.doneData, + filter: (props) => nonNullable(props), + fn: (props) => props!.pairings, + target: $pairings, +}); + +forward({ + from: connectFx.done, + to: connected, +}); + +sample({ + clock: disconnect, + source: { + client: $client, + session: $session, + }, + filter: ({ client, session }) => client !== null && session !== null, + fn: ({ client, session }) => ({ + client: client!, + session: session!, + }), + target: disconnectFx, +}); + +forward({ + from: disconnectFx.done, + to: reset, +}); + +sample({ + clock: sessionTopicUpdated, + source: walletModel.$activeAccounts, + fn: (activeAccounts, sessionTopic) => ({ + activeAccounts, + sessionTopic, + }), + target: sessionTopicUpdatedFx, +}); + +sample({ + clock: sessionTopicUpdatedFx.doneData, + source: walletModel.$accounts, + fn: (accounts, updatedAccounts) => { + const updatedMap = keyBy(updatedAccounts, 'id'); + + return accounts.map((account) => updatedMap[account.id] || account); + }, + target: walletModel.$accounts, +}); + +export const walletConnectModel = { + $client, + $session, + $uri, + $accounts, + $pairings, + events: { + connect, + disconnect, + sessionUpdated, + connected, + rejectConnection, + sessionTopicUpdated, + reset, + }, +}; diff --git a/src/renderer/features/operation/sign/ui/Signing/Signing.tsx b/src/renderer/features/operation/sign/ui/Signing/Signing.tsx index 0be71aabd3..6369932a42 100644 --- a/src/renderer/features/operation/sign/ui/Signing/Signing.tsx +++ b/src/renderer/features/operation/sign/ui/Signing/Signing.tsx @@ -2,6 +2,7 @@ import { useUnit } from 'effector-react'; import { SigningProps } from '../../model'; import { VaultSigning } from '../VaultSigning/VaultSigning'; +import { WalletConnect } from '../WalletConnect/WalletConnect'; import { SigningType } from '@renderer/shared/core'; import { walletModel } from '@renderer/entities/wallet'; @@ -9,6 +10,7 @@ export const SigningFlow: Record JSX.Eleme [SigningType.MULTISIG]: (props) => , [SigningType.PARITY_SIGNER]: (props) => , [SigningType.WATCH_ONLY]: () => null, + [SigningType.WALLET_CONNECT]: (props) => , }; export const Signing = (props: SigningProps) => { diff --git a/src/renderer/features/operation/sign/ui/WalletConnect/WalletConnect.tsx b/src/renderer/features/operation/sign/ui/WalletConnect/WalletConnect.tsx new file mode 100644 index 0000000000..837a05a83d --- /dev/null +++ b/src/renderer/features/operation/sign/ui/WalletConnect/WalletConnect.tsx @@ -0,0 +1,266 @@ +import { useEffect, useState } from 'react'; +import { UnsignedTransaction } from '@substrate/txwrapper-polkadot'; +import { useUnit } from 'effector-react'; + +import { SigningProps } from '@renderer/features/operation'; +import { ValidationErrors } from '@renderer/shared/lib/utils'; +import { useTransaction } from '@renderer/entities/transaction'; +import { useI18n } from '@renderer/app/providers'; +import { Button, ConfirmModal, Countdown, FootnoteText, SmallTitleText, StatusModal } from '@renderer/shared/ui'; +import { walletConnectModel, DEFAULT_POLKADOT_METHODS, getWalletConnectChains } from '@renderer/entities/walletConnect'; +import { chainsService } from '@renderer/entities/network'; +import { useCountdown, useToggle } from '@renderer/shared/lib/hooks'; +import wallet_connect_confirm from '@video/wallet_connect_confirm.mp4'; +import wallet_connect_confirm_webm from '@video/wallet_connect_confirm.webm'; +import { HexString } from '@renderer/shared/core'; +import { Animation } from '@renderer/shared/ui/Animation/Animation'; + +export const WalletConnect = ({ api, validateBalance, onGoBack, accounts, transactions, onResult }: SigningProps) => { + const { t } = useI18n(); + const { verifySignature, createPayload } = useTransaction(); + const [countdown, resetCountdown] = useCountdown(api); + + const session = useUnit(walletConnectModel.$session); + const client = useUnit(walletConnectModel.$client); + const connect = useUnit(walletConnectModel.events.connect); + const sessionUpdated = useUnit(walletConnectModel.events.sessionUpdated); + + const chains = chainsService.getChainsData(); + + const [txPayload, setTxPayload] = useState(); + const [unsignedTx, setUnsignedTx] = useState(); + const [isNeedUpdate, setIsNeedUpdate] = useState(false); + const [isReconnectModalOpen, setIsReconnectModalOpen] = useState(false); + const [isReconnectingModalOpen, setIsReconnectingModalOpen] = useState(false); + const [isConnectedModalOpen, setIsConnectedModalOpen] = useState(false); + const [isRejectedStatusOpen, toggleRejectedStatus] = useToggle(); + const [validationError, setValidationError] = useState(); + + const transaction = transactions[0]; + const account = accounts[0]; + + useEffect(() => { + if (txPayload || !client) return; + + const isCurrentSession = session && account && session.topic === account.signingExtras?.sessionTopic; + + if (isCurrentSession) { + setupTransaction().catch(() => console.warn('WalletConnect | setupTransaction() failed')); + } else { + const sessions = client.session.getAll(); + + const storedSession = sessions.find((s) => s.topic === account.signingExtras?.sessionTopic); + + if (storedSession) { + sessionUpdated(storedSession); + setIsNeedUpdate(true); + + setupTransaction().catch(() => console.warn('WalletConnect | setupTransaction() failed')); + } else { + setIsReconnectModalOpen(true); + } + } + }, [transaction, api]); + + useEffect(() => { + if (isNeedUpdate) { + setIsNeedUpdate(false); + + if (session?.topic) { + walletConnectModel.events.sessionTopicUpdated(session?.topic); + } + } + }, [session]); + + useEffect(() => { + if (unsignedTx) { + signTransaction(); + } + }, [unsignedTx]); + + useEffect(() => { + if (isReconnectingModalOpen && session?.topic === account.signingExtras?.sessionTopic) { + setIsReconnectingModalOpen(false); + setIsConnectedModalOpen(true); + } + }, [isReconnectingModalOpen]); + + useEffect(() => { + if (countdown <= 0) { + setValidationError(ValidationErrors.EXPIRED); + } + }, [countdown]); + + const setupTransaction = async (): Promise => { + try { + const { payload, unsigned } = await createPayload(transaction, api); + + setTxPayload(payload); + setUnsignedTx(unsigned); + + if (payload) { + resetCountdown(); + } + } catch (error) { + console.warn(error); + } + }; + + const reconnect = async () => { + setIsReconnectModalOpen(false); + setIsReconnectingModalOpen(true); + + connect({ + chains: getWalletConnectChains(chains), + pairing: { topic: account.signingExtras?.pairingTopic }, + }); + + setIsNeedUpdate(true); + }; + + const handleReconnect = () => { + reconnect() + .then(setupTransaction) + .catch(() => { + console.warn('WalletConnect | setupTransaction() failed'); + toggleRejectedStatus(); + }); + }; + + const signTransaction = async () => { + if (!api || !client || !session) return; + + try { + const result = await client.request<{ + payload: string; + signature: HexString; + }>({ + // eslint-disable-next-line i18next/no-literal-string + chainId: `polkadot:${transaction.chainId.slice(2, 34)}`, + topic: session.topic, + request: { + method: DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION, + params: { + address: transaction.address, + transactionPayload: unsignedTx, + }, + }, + }); + + if (result.signature) { + handleSignature(result.signature); + } + } catch (e) { + console.warn(e); + toggleRejectedStatus(); + } + }; + + const handleSignature = async (signature: HexString) => { + const isVerified = txPayload && verifySignature(txPayload, signature as HexString, accounts[0].accountId); + + const balanceValidationError = validateBalance && (await validateBalance()); + + if (isVerified && balanceValidationError) { + setValidationError(balanceValidationError || ValidationErrors.INVALID_SIGNATURE); + } else { + if (unsignedTx) { + onResult([signature], [unsignedTx]); + } + } + }; + + const walletName = session?.peer.metadata.name || t('operation.walletConnect.defaultWalletName'); + + const getStatusProps = () => { + if (isReconnectingModalOpen) { + return { + title: t('operation.walletConnect.reconnect.reconnecting'), + content: , + onClose: onGoBack, + }; + } + + if (isConnectedModalOpen) { + return { + title: t('operation.walletConnect.reconnect.connected'), + content: , + onClose: () => setIsConnectedModalOpen(false), + }; + } + + if (isRejectedStatusOpen) { + return { + title: t('operation.walletConnect.rejected'), + content: , + onClose: onGoBack, + }; + } + + return { + title: '', + content: <>, + onClose: () => {}, + }; + }; + + return ( +
+ + {t('operation.walletConnect.signTitle', { + walletName, + })} + + + + +
+ + + {validationError === ValidationErrors.EXPIRED && ( + <> +
+
+ {t('operation.walletConnect.expiredDescription')} + +
+ + )} +
+ +
+ +
+ + + + {t('operation.walletConnect.reconnect.title', { + walletName, + })} + + + {t('operation.walletConnect.reconnect.description')} + + + + +
+ ); +}; diff --git a/src/renderer/features/wallets/WalletSelect/WalletMenu.tsx b/src/renderer/features/wallets/WalletSelect/WalletMenu.tsx index dfbe150934..41fd5982f3 100644 --- a/src/renderer/features/wallets/WalletSelect/WalletMenu.tsx +++ b/src/renderer/features/wallets/WalletSelect/WalletMenu.tsx @@ -27,6 +27,8 @@ export const WalletMenu = ({ children }: PropsWithChildren) => { if (walletUtils.isPolkadotVault(wallet)) groupIndex = WalletType.POLKADOT_VAULT; if (walletUtils.isMultisig(wallet)) groupIndex = WalletType.MULTISIG; if (walletUtils.isWatchOnly(wallet)) groupIndex = WalletType.WATCH_ONLY; + if (walletUtils.isNovaWallet(wallet)) groupIndex = WalletType.NOVA_WALLET; + if (walletUtils.isWalletConnect(wallet)) groupIndex = WalletType.WALLET_CONNECT; if (groupIndex && includes(wallet.name, query)) { acc[groupIndex].push(wallet); } @@ -37,6 +39,8 @@ export const WalletMenu = ({ children }: PropsWithChildren) => { [WalletType.POLKADOT_VAULT]: [], [WalletType.MULTISIG]: [], [WalletType.WATCH_ONLY]: [], + [WalletType.NOVA_WALLET]: [], + [WalletType.WALLET_CONNECT]: [], }, ); }; @@ -60,6 +64,18 @@ export const WalletMenu = ({ children }: PropsWithChildren) => { onClick: () => walletProviderModel.events.walletTypeSet(WalletType.MULTISIG), iconName: 'multisig', }, + { + id: 'nova-wallet', + title: t('wallets.addNovaWallet'), + onClick: () => walletProviderModel.events.walletTypeSet(WalletType.NOVA_WALLET), + iconName: 'novaWallet', + }, + { + id: 'wallet-connect', + title: t('wallets.addWalletConnect'), + onClick: () => walletProviderModel.events.walletTypeSet(WalletType.WALLET_CONNECT), + iconName: 'walletConnect', + }, ]; const selectWallet = (walletId: Wallet['id'], closeMenu: () => void) => { diff --git a/src/renderer/features/wallets/WalletSelect/common/constants.ts b/src/renderer/features/wallets/WalletSelect/common/constants.ts index 1c89197913..a6594a155e 100644 --- a/src/renderer/features/wallets/WalletSelect/common/constants.ts +++ b/src/renderer/features/wallets/WalletSelect/common/constants.ts @@ -5,6 +5,8 @@ export const GroupLabels: Record = { [WalletType.POLKADOT_VAULT]: 'wallets.paritySignerLabel', [WalletType.MULTISHARD_PARITY_SIGNER]: 'wallets.paritySignerLabel', [WalletType.SINGLE_PARITY_SIGNER]: 'wallets.paritySignerLabel', + [WalletType.WALLET_CONNECT]: 'wallets.walletConnectLabel', + [WalletType.NOVA_WALLET]: 'wallets.novaWalletLabel', [WalletType.MULTISIG]: 'wallets.multisigLabel', [WalletType.WATCH_ONLY]: 'wallets.watchOnlyLabel', }; @@ -13,6 +15,8 @@ export const GroupIcons: Record = { [WalletType.POLKADOT_VAULT]: 'vault', [WalletType.MULTISHARD_PARITY_SIGNER]: 'vault', [WalletType.SINGLE_PARITY_SIGNER]: 'vault', + [WalletType.WALLET_CONNECT]: 'walletConnect', + [WalletType.NOVA_WALLET]: 'novaWallet', [WalletType.MULTISIG]: 'multisig', [WalletType.WATCH_ONLY]: 'watchOnly', }; diff --git a/src/renderer/pages/Onboarding/WalletConnect/ManageStep/ManageStep.tsx b/src/renderer/pages/Onboarding/WalletConnect/ManageStep/ManageStep.tsx new file mode 100644 index 0000000000..3a4d1a2e10 --- /dev/null +++ b/src/renderer/pages/Onboarding/WalletConnect/ManageStep/ManageStep.tsx @@ -0,0 +1,197 @@ +import cn from 'classnames'; +import { useEffect, useState } from 'react'; +import { Controller, useForm, SubmitHandler } from 'react-hook-form'; + +import { useI18n, useStatusContext } from '@renderer/app/providers'; +import { + AccountId, + Chain, + ChainType, + ErrorType, + NoID, + SigningType, + WalletType, + AccountType, + WalletConnectAccount, +} from '@renderer/shared/core'; +import { Button, Input, InputHint, HeaderTitleText, SmallTitleText, Icon } from '@renderer/shared/ui'; +import { toAccountId } from '@renderer/shared/lib/utils'; +import { chainsService } from '@renderer/entities/network'; +import { IconNames } from '@renderer/shared/ui/Icon/data'; +import { MultiAccountsList, walletModel } from '@renderer/entities/wallet'; + +const WalletLogo: Record = { + [WalletType.WALLET_CONNECT]: 'walletConnectOnboarding', + [WalletType.NOVA_WALLET]: 'novaWalletOnboarding', +}; + +type WalletForm = { + walletName: string; +}; + +type WalletTypeName = WalletType.NOVA_WALLET | WalletType.WALLET_CONNECT; + +type Props = { + accounts: string[]; + pairingTopic: string; + sessionTopic: string; + type: WalletTypeName; + onBack: () => void; + onComplete: () => void; +}; + +const ManageStep = ({ accounts, type, pairingTopic, sessionTopic, onBack, onComplete }: Props) => { + const { t } = useI18n(); + const { showStatus } = useStatusContext(); + + const [chains, setChains] = useState([]); + const [accountsList, setAccountsList] = useState<{ chain: Chain; accountId: AccountId }[]>([]); + + const { + handleSubmit, + control, + reset, + formState: { errors, isValid }, + } = useForm({ + mode: 'onChange', + defaultValues: { walletName: '' }, + }); + + useEffect(() => { + const chains = chainsService.getChainsData(); + setChains(chainsService.sortChains(chains)); + }, []); + + useEffect(() => { + const list = chains.reduce<{ chain: Chain; accountId: AccountId }[]>((acc, chain) => { + const account = accounts.find((account) => { + const [_, chainId] = account.split(':'); + + return chain.chainId.includes(chainId); + }); + + const [_, _chainId, address] = account?.split(':') || []; + + if (address) { + const accountId = toAccountId(address); + + acc.push({ + chain, + accountId, + }); + } + + return acc; + }, []); + + setAccountsList(list.filter(Boolean)); + }, [chains.length]); + + // TODO: Rewrite with effector forms + const submitHandler: SubmitHandler = async ({ walletName }) => { + walletModel.events.walletConnectCreated({ + wallet: { + name: walletName.trim(), + type, + signingType: SigningType.WALLET_CONNECT, + }, + accounts: accounts.map((account) => { + const [_, chainId, address] = account.split(':'); + const chain = chains.find((chain) => chain.chainId.includes(chainId)); + const accountId = toAccountId(address); + + return { + name: walletName.trim(), + accountId, + type: AccountType.WALLET_CONNECT, + chainType: ChainType.SUBSTRATE, + chainId: chain?.chainId, + signingExtras: { + pairingTopic, + sessionTopic, + }, + } as Omit, 'walletId'>; + }), + }); + + reset(); + + showStatus({ + title: walletName.trim(), + description: t('onboarding.walletConnect.pairedDescription'), + content: ( +
+ +
+ +
+ +
+ ), + }); + + onComplete(); + }; + + const goBack = () => { + reset(); + onBack(); + }; + + const Title = { + [WalletType.WALLET_CONNECT]: t('onboarding.walletConnect.title'), + [WalletType.NOVA_WALLET]: t('onboarding.novaWallet.title'), + }; + + return ( + <> +
+ {Title[type]} + {t('onboarding.walletConnect.manageTitle')} + +
+ ( +
+ + + {t('onboarding.watchOnly.walletNameMaxLenError')} + + + {t('onboarding.watchOnly.walletNameRequiredError')} + +
+ )} + /> + +
+ + + +
+ +
+ +
+ {t('onboarding.vault.accountsTitle')} + +
+ + ); +}; + +export default ManageStep; diff --git a/src/renderer/pages/Onboarding/WalletConnect/NovaWallet.tsx b/src/renderer/pages/Onboarding/WalletConnect/NovaWallet.tsx new file mode 100644 index 0000000000..2489ae24a4 --- /dev/null +++ b/src/renderer/pages/Onboarding/WalletConnect/NovaWallet.tsx @@ -0,0 +1,143 @@ +import QRCodeStyling from 'qr-code-styling'; +import { useEffect, useRef, useState } from 'react'; +import { useUnit } from 'effector-react'; + +import { useI18n } from '@renderer/app/providers'; +import { BaseModal, Button, HeaderTitleText, SmallTitleText } from '@renderer/shared/ui'; +import { Animation } from '@renderer/shared/ui/Animation/Animation'; +import ManageStep from './ManageStep/ManageStep'; +import novawallet_onboarding_tutorial from '@video/novawallet_onboarding_tutorial.mp4'; +import novawallet_onboarding_tutorial_webm from '@video/novawallet_onboarding_tutorial.webm'; +import { usePrevious } from '@renderer/shared/lib/hooks'; +import { getWalletConnectChains, walletConnectModel } from '@renderer/entities/walletConnect'; +import { chainsService } from '@renderer/entities/network'; +import { wcOnboardingModel } from '@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model'; +import { EXPIRE_TIMEOUT, NWQRConfig, Step } from './common/const'; +import { useStatusContext } from '@renderer/app/providers/context/StatusContext'; +import { WalletType } from '@renderer/shared/core'; + +type Props = { + isOpen: boolean; + size?: number; + onClose: () => void; + onComplete: () => void; +}; + +const qrCode = new QRCodeStyling(NWQRConfig); + +export const NovaWallet = ({ isOpen, onClose, onComplete }: Props) => { + const { t } = useI18n(); + const { showStatus } = useStatusContext(); + + const session = useUnit(walletConnectModel.$session); + const client = useUnit(walletConnectModel.$client); + const pairings = useUnit(walletConnectModel.$pairings); + const uri = useUnit(walletConnectModel.$uri); + const connect = useUnit(walletConnectModel.events.connect); + const disconnect = useUnit(walletConnectModel.events.disconnect); + const step = useUnit(wcOnboardingModel.$step); + const startOnboarding = useUnit(wcOnboardingModel.events.startOnboarding); + + const previousPairings = usePrevious(pairings); + + const [pairingTopic, setPairingTopic] = useState(); + + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + qrCode.append(ref.current); + } + }, []); + + useEffect(() => { + qrCode.update({ + data: uri, + }); + }, [uri]); + + useEffect(() => { + let timeout: any; + if (isOpen) { + startOnboarding(); + + timeout = setTimeout(onClose, EXPIRE_TIMEOUT); + } + + return () => { + timeout && clearTimeout(timeout); + }; + }, [isOpen]); + + useEffect(() => { + if (step === Step.REJECT) { + showStatus({ + title: t('onboarding.walletConnect.rejected'), + content: , + }); + onClose(); + } + }, [step]); + + useEffect(() => { + if (client && isOpen) { + const chains = getWalletConnectChains(chainsService.getChainsData()); + connect({ chains }); + } + }, [client, isOpen]); + + useEffect(() => { + const newPairing = pairings?.find((p) => !previousPairings?.find((pp) => pp.topic === p.topic)); + + if (newPairing) { + setPairingTopic(newPairing.topic); + } + }, [pairings.length]); + + return ( + + {step === Step.SCAN && qrCode && ( + <> +
+ {t('onboarding.novaWallet.title')} + {t('onboarding.novaWallet.scanTitle')} + +
+
+
+ +
+ +
+
+ +
+ +
+ + )} + + {step === Step.MANAGE && session && pairingTopic && ( + + )} +
+ ); +}; diff --git a/src/renderer/pages/Onboarding/WalletConnect/WalletConnect.tsx b/src/renderer/pages/Onboarding/WalletConnect/WalletConnect.tsx new file mode 100644 index 0000000000..c6eb18abd8 --- /dev/null +++ b/src/renderer/pages/Onboarding/WalletConnect/WalletConnect.tsx @@ -0,0 +1,146 @@ +import QRCodeStyling from 'qr-code-styling'; +import { useEffect, useRef, useState } from 'react'; +import { useUnit } from 'effector-react'; + +import { useI18n } from '@renderer/app/providers'; +import { BaseModal, Button, HeaderTitleText, SmallTitleText } from '@renderer/shared/ui'; +import { Animation } from '@renderer/shared/ui/Animation/Animation'; +import ManageStep from './ManageStep/ManageStep'; +import novawallet_onboarding_tutorial from '@video/novawallet_onboarding_tutorial.mp4'; +import novawallet_onboarding_tutorial_webm from '@video/novawallet_onboarding_tutorial.webm'; +import { usePrevious } from '@renderer/shared/lib/hooks'; +import { getWalletConnectChains, walletConnectModel } from '@renderer/entities/walletConnect'; +import { chainsService } from '@renderer/entities/network'; +import { wcOnboardingModel } from '@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model'; +import { WCQRConfig, Step, EXPIRE_TIMEOUT } from './common/const'; +import { useStatusContext } from '@renderer/app/providers/context/StatusContext'; +import { WalletType } from '@renderer/shared/core'; + +type Props = { + isOpen: boolean; + size?: number; + onClose: () => void; + onComplete: () => void; +}; + +const qrCode = new QRCodeStyling(WCQRConfig); + +export const WalletConnect = ({ isOpen, onClose, onComplete }: Props) => { + const { t } = useI18n(); + + const session = useUnit(walletConnectModel.$session); + const client = useUnit(walletConnectModel.$client); + const pairings = useUnit(walletConnectModel.$pairings); + const uri = useUnit(walletConnectModel.$uri); + const connect = useUnit(walletConnectModel.events.connect); + const disconnect = useUnit(walletConnectModel.events.disconnect); + const step = useUnit(wcOnboardingModel.$step); + const startOnboarding = useUnit(wcOnboardingModel.events.startOnboarding); + + const previousPairings = usePrevious(pairings); + + const [pairingTopic, setPairingTopic] = useState(); + + const { showStatus } = useStatusContext(); + + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + qrCode.append(ref.current); + } + }, []); + + useEffect(() => { + qrCode.update({ + data: uri, + }); + }, [uri]); + + useEffect(() => { + let timeout: any; + if (isOpen) { + startOnboarding(); + + timeout = setTimeout(onClose, EXPIRE_TIMEOUT); + } + + return () => { + timeout && clearTimeout(timeout); + }; + }, [isOpen]); + + useEffect(() => { + if (step === Step.REJECT) { + showStatus({ + title: t('onboarding.walletConnect.rejected'), + content: , + }); + onClose(); + } + }, [step]); + + useEffect(() => { + if (client && isOpen) { + const chains = getWalletConnectChains(chainsService.getChainsData()); + connect({ chains }); + } + }, [client, isOpen]); + + useEffect(() => { + const newPairing = pairings?.find((p) => !previousPairings?.find((pp) => pp.topic === p.topic)); + + if (newPairing) { + setPairingTopic(newPairing.topic); + } + }, [pairings.length]); + + return ( + <> + + {step === Step.SCAN && qrCode && ( + <> +
+ {t('onboarding.walletConnect.title')} + {t('onboarding.walletConnect.scanTitle')} + +
+
+
+ +
+ +
+
+ +
+ +
+ + )} + + {step === Step.MANAGE && session && pairingTopic && ( + + )} +
+ + ); +}; diff --git a/src/renderer/pages/Onboarding/WalletConnect/common/const.ts b/src/renderer/pages/Onboarding/WalletConnect/common/const.ts new file mode 100644 index 0000000000..7399b88ab0 --- /dev/null +++ b/src/renderer/pages/Onboarding/WalletConnect/common/const.ts @@ -0,0 +1,56 @@ +import { Options } from 'qr-code-styling'; + +import WalletTypeImages from '@renderer/shared/ui/Icon/data/walletType'; + +export const enum Step { + SCAN, + MANAGE, + REJECT, + SUCCESS, +} + +const QrConfig = { + width: 300, + height: 300, + imageOptions: { + hideBackgroundDots: true, + imageSize: 1, + margin: 10, + }, + qrOptions: { + typeNumber: 0, + mode: 'Byte', + errorCorrectionLevel: 'L', + }, + type: 'svg', + dotsOptions: { + type: 'dots', + color: '#ff009d', + gradient: { + type: 'linear', + rotation: 0.7853981633974483, + colorStops: [ + { offset: 0, color: '#3384fe' }, + { offset: 1, color: '#075dc1' }, + ], + }, + }, + backgroundOptions: { color: '#ffffff' }, + cornersSquareOptions: { + type: 'extra-rounded', + color: '#000000', + }, + cornersDotOptions: { type: undefined, color: '#000000', gradient: undefined }, +} as Partial; + +export const WCQRConfig = { + ...QrConfig, + image: WalletTypeImages.walletConnectOnboarding.img, +}; + +export const NWQRConfig = { + ...QrConfig, + image: WalletTypeImages.novaWalletOnboarding.img, +}; + +export const EXPIRE_TIMEOUT = 5 * 60 * 1000; diff --git a/src/renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model.ts b/src/renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model.ts new file mode 100644 index 0000000000..3ef7d53148 --- /dev/null +++ b/src/renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model.ts @@ -0,0 +1,27 @@ +import { createEvent, createStore, sample } from 'effector'; + +import { walletConnectModel } from '@renderer/entities/walletConnect'; +import { Step } from '../common/const'; + +const startOnboarding = createEvent(); + +const $step = createStore(Step.SCAN).reset(startOnboarding); + +sample({ + clock: walletConnectModel.events.connected, + fn: () => Step.MANAGE, + target: $step, +}); + +sample({ + clock: walletConnectModel.events.rejectConnection, + fn: () => Step.REJECT, + target: $step, +}); + +export const wcOnboardingModel = { + $step, + events: { + startOnboarding, + }, +}; diff --git a/src/renderer/pages/Onboarding/Welcome/Welcome.tsx b/src/renderer/pages/Onboarding/Welcome/Welcome.tsx index 1b2c5ba2c5..a4340c69c7 100644 --- a/src/renderer/pages/Onboarding/Welcome/Welcome.tsx +++ b/src/renderer/pages/Onboarding/Welcome/Welcome.tsx @@ -47,30 +47,30 @@ export const Welcome = () => { /> walletProviderModel.events.walletTypeSet(WalletType.WATCH_ONLY)} + title={t('onboarding.welcome.novaWalletTitle')} + description={t('onboarding.welcome.novaWalletDescription')} + iconName="novaWalletOnboarding" + onClick={() => walletProviderModel.events.walletTypeSet(WalletType.NOVA_WALLET)} /> walletProviderModel.events.walletTypeSet(WalletType.WALLET_CONNECT)} /> walletProviderModel.events.walletTypeSet(WalletType.WATCH_ONLY)} />
diff --git a/src/renderer/pages/Onboarding/Welcome/WelcomeCard.tsx b/src/renderer/pages/Onboarding/Welcome/WelcomeCard.tsx index 8347a074a4..22f5474542 100644 --- a/src/renderer/pages/Onboarding/Welcome/WelcomeCard.tsx +++ b/src/renderer/pages/Onboarding/Welcome/WelcomeCard.tsx @@ -24,11 +24,7 @@ export const WelcomeCard = ({ title, description, iconName, disabled, onClick }: )} onClick={onClick} > - +
diff --git a/src/renderer/pages/Operations/components/ActionSteps/Submit.tsx b/src/renderer/pages/Operations/components/ActionSteps/Submit.tsx index 2d0e796957..0d5d79f974 100644 --- a/src/renderer/pages/Operations/components/ActionSteps/Submit.tsx +++ b/src/renderer/pages/Operations/components/ActionSteps/Submit.tsx @@ -6,7 +6,7 @@ import { useI18n, useMatrix, useMultisigChainContext } from '@renderer/app/provi import { useMultisigTx, useMultisigEvent } from '@renderer/entities/multisig'; import { toAccountId } from '@renderer/shared/lib/utils'; import { useToggle } from '@renderer/shared/lib/hooks'; -import { Button } from '@renderer/shared/ui'; +import { Button, StatusModal } from '@renderer/shared/ui'; import type { Account, HexString } from '@renderer/shared/core'; import { MultisigEvent, @@ -165,12 +165,8 @@ export const Submit = ({ }; return ( - + {errorMessage && } - + ); }; diff --git a/src/renderer/pages/Operations/components/ShortTransactionInfo.test.tsx b/src/renderer/pages/Operations/components/ShortTransactionInfo.test.tsx index 50b173d3b7..01141d91d8 100644 --- a/src/renderer/pages/Operations/components/ShortTransactionInfo.test.tsx +++ b/src/renderer/pages/Operations/components/ShortTransactionInfo.test.tsx @@ -4,6 +4,15 @@ import { TransactionAmount } from './TransactionAmount'; import { Transaction, TransactionType } from '@renderer/entities/transaction'; import { TEST_ADDRESS, TEST_CHAIN_ID } from '@renderer/shared/lib/utils'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + jest.mock('@renderer/app/providers', () => ({ useI18n: jest.fn().mockReturnValue({ t: (key: string) => key, diff --git a/src/renderer/pages/Operations/components/TransactionTitle/TransactionTitle.test.tsx b/src/renderer/pages/Operations/components/TransactionTitle/TransactionTitle.test.tsx index 32959a316f..b2f8ee8cd1 100644 --- a/src/renderer/pages/Operations/components/TransactionTitle/TransactionTitle.test.tsx +++ b/src/renderer/pages/Operations/components/TransactionTitle/TransactionTitle.test.tsx @@ -4,6 +4,15 @@ import { Transaction, TransactionType } from '@renderer/entities/transaction'; import { TEST_ADDRESS, TEST_CHAIN_ID } from '@renderer/shared/lib/utils'; import { TransactionTitle } from './TransactionTitle'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + jest.mock('@renderer/app/providers', () => ({ useI18n: jest.fn().mockReturnValue({ t: (key: string) => key, diff --git a/src/renderer/pages/Operations/components/modals/ApproveTx.tsx b/src/renderer/pages/Operations/components/modals/ApproveTx.tsx index 3aa6baa891..26d841f8b5 100644 --- a/src/renderer/pages/Operations/components/modals/ApproveTx.tsx +++ b/src/renderer/pages/Operations/components/modals/ApproveTx.tsx @@ -4,7 +4,7 @@ import { Weight } from '@polkadot/types/interfaces'; import { BN } from '@polkadot/util'; import { useUnit } from 'effector-react'; -import { BaseModal, Button, Icon } from '@renderer/shared/ui'; +import { BaseModal, Button } from '@renderer/shared/ui'; import { useI18n } from '@renderer/app/providers'; import { MultisigTransactionDS } from '@renderer/shared/api/storage'; import { useToggle } from '@renderer/shared/lib/hooks'; @@ -18,8 +18,15 @@ import { SignatorySelectModal } from './SignatorySelectModal'; import { useMultisigEvent } from '@renderer/entities/multisig'; import { Signing } from '@renderer/features/operation'; import { OperationTitle } from '@renderer/components/common'; -import type { Address, HexString, Timepoint, MultisigAccount, Account } from '@renderer/shared/core'; import { walletModel, accountUtils, walletUtils } from '@renderer/entities/wallet'; +import { + type Address, + type HexString, + type Timepoint, + type MultisigAccount, + type Account, + WalletType, +} from '@renderer/shared/core'; import { OperationResult, Transaction, @@ -30,6 +37,7 @@ import { isXcmTransaction, MAX_WEIGHT, } from '@renderer/entities/transaction'; +import { SignButton } from '@renderer/entities/operation/ui/SignButton'; import { priceProviderModel } from '@renderer/entities/price'; type Props = { @@ -240,13 +248,12 @@ const ApproveTx = ({ tx, account, connection }: Props) => { {activeStep === Step.CONFIRMATION && ( <> - + /> )} diff --git a/src/renderer/pages/Operations/components/modals/RejectTx.tsx b/src/renderer/pages/Operations/components/modals/RejectTx.tsx index ecd805b226..b6e6f98b21 100644 --- a/src/renderer/pages/Operations/components/modals/RejectTx.tsx +++ b/src/renderer/pages/Operations/components/modals/RejectTx.tsx @@ -3,7 +3,7 @@ import { UnsignedTransaction } from '@substrate/txwrapper-polkadot'; import { BN } from '@polkadot/util'; import { useUnit } from 'effector-react'; -import { BaseModal, Button, Icon } from '@renderer/shared/ui'; +import { BaseModal, Button } from '@renderer/shared/ui'; import { useI18n } from '@renderer/app/providers'; import { MultisigTransactionDS } from '@renderer/shared/api/storage'; import { useToggle } from '@renderer/shared/lib/hooks'; @@ -16,9 +16,16 @@ import { Submit } from '../ActionSteps/Submit'; import { Confirmation } from '../ActionSteps/Confirmation'; import { Signing } from '@renderer/features/operation'; import { OperationTitle } from '@renderer/components/common'; -import type { MultisigAccount, Account, Address, HexString, Timepoint } from '@renderer/shared/core'; import { walletModel, walletUtils } from '@renderer/entities/wallet'; import { priceProviderModel } from '@renderer/entities/price'; +import { + type MultisigAccount, + type Account, + type Address, + type HexString, + type Timepoint, + WalletType, +} from '@renderer/shared/core'; import { Transaction, TransactionType, @@ -27,6 +34,7 @@ import { validateBalance, isXcmTransaction, } from '@renderer/entities/transaction'; +import { SignButton } from '@renderer/entities/operation/ui/SignButton'; type Props = { tx: MultisigTransactionDS; @@ -191,13 +199,11 @@ const RejectTx = ({ tx, account, connection }: Props) => { {activeStep === Step.CONFIRMATION && ( <> - + /> )} {activeStep === Step.SIGNING && rejectTx && connection.api && signAccount && ( diff --git a/src/renderer/pages/Settings/Networks/components/NetworkList/NetworkList.test.tsx b/src/renderer/pages/Settings/Networks/components/NetworkList/NetworkList.test.tsx index acf8b0fa8a..5aa61d10de 100644 --- a/src/renderer/pages/Settings/Networks/components/NetworkList/NetworkList.test.tsx +++ b/src/renderer/pages/Settings/Networks/components/NetworkList/NetworkList.test.tsx @@ -4,6 +4,15 @@ import { ConnectionStatus, ConnectionType } from '@renderer/shared/core'; import { ExtendedChain } from '@renderer/entities/network'; import { NetworkList } from './NetworkList'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('pages/Settings/Networks/NetworkList', () => { const children = () => 'children'; const networks: ExtendedChain[] = [ diff --git a/src/renderer/pages/Staking/Operations/components/Confirmation/Confirmation.tsx b/src/renderer/pages/Staking/Operations/components/Confirmation/Confirmation.tsx index eda22d0eed..4ebd0ea57c 100644 --- a/src/renderer/pages/Staking/Operations/components/Confirmation/Confirmation.tsx +++ b/src/renderer/pages/Staking/Operations/components/Confirmation/Confirmation.tsx @@ -1,12 +1,13 @@ import { BN, BN_ZERO } from '@polkadot/util'; import { ApiPromise } from '@polkadot/api'; import { PropsWithChildren, useState, useEffect } from 'react'; +import { useUnit } from 'effector-react'; import { Icon, Button, FootnoteText, CaptionText, InputHint } from '@renderer/shared/ui'; import { useI18n } from '@renderer/app/providers'; import { useToggle } from '@renderer/shared/lib/hooks'; import { Validator } from '@renderer/shared/core/types/validator'; -import { AddressWithExplorers, accountUtils } from '@renderer/entities/wallet'; +import { AddressWithExplorers, accountUtils, walletModel } from '@renderer/entities/wallet'; import { AssetBalance } from '@renderer/entities/asset'; import { MultisigTxInitStatus, @@ -20,9 +21,10 @@ import ValidatorsModal from '../Modals/ValidatorsModal/ValidatorsModal'; import { DestinationType } from '../../common/types'; import { cnTw } from '@renderer/shared/lib/utils'; import { useMultisigTx } from '@renderer/entities/multisig'; -import { RewardsDestination } from '@renderer/shared/core'; +import { RewardsDestination, WalletType } from '@renderer/shared/core'; import type { Account, Asset, Explorer } from '@renderer/shared/core'; import { AssetFiatBalance } from '@renderer/entities/price/ui/AssetFiatBalance'; +import { SignButton } from '@renderer/entities/operation/ui/SignButton'; const ActionStyle = 'group hover:bg-action-background-hover px-2 py-1 rounded'; @@ -61,6 +63,7 @@ export const Confirmation = ({ const { t } = useI18n(); const { getMultisigTxs } = useMultisigTx({}); const { getTransactionHash } = useTransaction(); + const activeWallet = useUnit(walletModel.$activeWallet); const [isAccountsOpen, toggleAccounts] = useToggle(); const [isValidatorsOpen, toggleValidators] = useToggle(); @@ -222,13 +225,11 @@ export const Confirmation = ({ - + />
diff --git a/src/renderer/pages/Staking/Overview/Overview.tsx b/src/renderer/pages/Staking/Overview/Overview.tsx index f586042c92..6a429e89b4 100644 --- a/src/renderer/pages/Staking/Overview/Overview.tsx +++ b/src/renderer/pages/Staking/Overview/Overview.tsx @@ -109,8 +109,10 @@ export const Overview = () => { const isMultisig = walletUtils.isMultisig(activeWallet); const isSingleShard = walletUtils.isSingleShard(activeWallet); const isSingleMultishard = walletUtils.isMultiShard(activeWallet) && addresses.length === 1; + const isNovaWallet = walletUtils.isNovaWallet(activeWallet); + const isWalletConnect = walletUtils.isWalletConnect(activeWallet); - if (isMultisig || isSingleShard || isSingleMultishard) { + if (isMultisig || isSingleShard || isSingleMultishard || isNovaWallet || isWalletConnect) { setSelectedNominators([addresses[0]]); } else { setSelectedNominators([]); diff --git a/src/renderer/shared/api/storage/service/storageService.ts b/src/renderer/shared/api/storage/service/storageService.ts index 1d4f7a6f96..8f7f2829b7 100644 --- a/src/renderer/shared/api/storage/service/storageService.ts +++ b/src/renderer/shared/api/storage/service/storageService.ts @@ -69,6 +69,20 @@ class StorageService { } } + async updateAll(items: Array & { id: K }>): Promise { + try { + const updates = items.map((item) => { + return this.dexieTable.update(item.id, item); + }); + + return Promise.all(updates); + } catch (error) { + console.log('Error updating object - ', error); + + return Promise.resolve(undefined); + } + } + delete(id: K): Promise { return this.dexieTable.delete(id); } diff --git a/src/renderer/shared/core/index.ts b/src/renderer/shared/core/index.ts index 484d8ebc26..7b9dfa963c 100644 --- a/src/renderer/shared/core/index.ts +++ b/src/renderer/shared/core/index.ts @@ -10,7 +10,7 @@ export type { Wallet, WalletFamily } from './types/wallet'; export { WalletType, SigningType } from './types/wallet'; export { AccountType, KeyType } from './types/account'; -export type { Account, BaseAccount, ChainAccount, MultisigAccount } from './types/account'; +export type { Account, BaseAccount, ChainAccount, MultisigAccount, WalletConnectAccount } from './types/account'; export { AssetType, StakingType } from './types/asset'; export type { Asset, OrmlExtras, StatemineExtras } from './types/asset'; diff --git a/src/renderer/shared/core/types/account.ts b/src/renderer/shared/core/types/account.ts index bd2dd8e1ab..0c021c08cc 100644 --- a/src/renderer/shared/core/types/account.ts +++ b/src/renderer/shared/core/types/account.ts @@ -42,11 +42,11 @@ export type MultisigAccount = BaseAccount & { creatorAccountId: AccountId; }; -// export type WalletConnectAccount = Omit & { -// chainId: ChainId; -// }; +export type WalletConnectAccount = Omit & { + chainId: ChainId; +}; -export type Account = BaseAccount | ChainAccount | MultisigAccount; +export type Account = BaseAccount | ChainAccount | MultisigAccount | WalletConnectAccount; export const enum AccountType { BASE = 'base', @@ -54,7 +54,7 @@ export const enum AccountType { // SHARDED = 'sharded', // SHARD = 'shard', MULTISIG = 'multisig', - // WALLET_CONNECT = 'wallet_connect', + WALLET_CONNECT = 'wallet_connect', } export const enum KeyType { diff --git a/src/renderer/shared/core/types/wallet.ts b/src/renderer/shared/core/types/wallet.ts index bc18749fda..4993c0d508 100644 --- a/src/renderer/shared/core/types/wallet.ts +++ b/src/renderer/shared/core/types/wallet.ts @@ -12,21 +12,26 @@ export const enum WalletType { WATCH_ONLY = 'wallet_wo', POLKADOT_VAULT = 'wallet_pv', MULTISIG = 'wallet_ms', - // WALLET_CONNECT = 'wallet_wc', - // NOVA_WALLET = 'wallet_nw', + WALLET_CONNECT = 'wallet_wc', + NOVA_WALLET = 'wallet_nw', // Legacy MULTISHARD_PARITY_SIGNER = 'wallet_mps', SINGLE_PARITY_SIGNER = 'wallet_sps', } -export type WalletFamily = WalletType.POLKADOT_VAULT | WalletType.MULTISIG | WalletType.WATCH_ONLY; +export type WalletFamily = + | WalletType.POLKADOT_VAULT + | WalletType.MULTISIG + | WalletType.WATCH_ONLY + | WalletType.WALLET_CONNECT + | WalletType.NOVA_WALLET; export const enum SigningType { WATCH_ONLY = 'signing_wo', PARITY_SIGNER = 'signing_ps', MULTISIG = 'signing_ms', // POLKADOT_VAULT = 'signing_pv', - // WALLET_CONNECT = 'signing_wc', + WALLET_CONNECT = 'signing_wc', // NOVA_WALLET = 'signing_nw', } diff --git a/src/renderer/shared/lib/utils/validation.ts b/src/renderer/shared/lib/utils/validation.ts index 30d3dcf136..f545d94018 100644 --- a/src/renderer/shared/lib/utils/validation.ts +++ b/src/renderer/shared/lib/utils/validation.ts @@ -5,4 +5,5 @@ export const enum ValidationErrors { INSUFFICIENT_BALANCE, INSUFFICIENT_BALANCE_FOR_FEE, INVALID_SIGNATURE, + EXPIRED, } diff --git a/src/renderer/shared/ui/Alert/Alert.test.tsx b/src/renderer/shared/ui/Alert/Alert.test.tsx index 790fda79c8..6cbaab499c 100644 --- a/src/renderer/shared/ui/Alert/Alert.test.tsx +++ b/src/renderer/shared/ui/Alert/Alert.test.tsx @@ -3,8 +3,14 @@ import noop from 'lodash/noop'; import Alert from './Alert'; -// jest.mock('dexie-react-hooks'); -// jest.mock('dexie'); +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); describe('ui/Alert', () => { test('should render title and items', () => { diff --git a/src/renderer/shared/ui/Animation/Animation.tsx b/src/renderer/shared/ui/Animation/Animation.tsx index e1bf914233..f4ee8d474f 100644 --- a/src/renderer/shared/ui/Animation/Animation.tsx +++ b/src/renderer/shared/ui/Animation/Animation.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from 'react'; import { useLottie } from 'lottie-react'; import Animations from './Data'; @@ -5,7 +6,7 @@ import Animations from './Data'; export type AnimationNames = keyof typeof Animations; export type Props = { - animation: Object; + variant: AnimationNames; width?: number; height?: number; loop?: boolean; @@ -13,7 +14,9 @@ export type Props = { className?: string; }; -export const Animation = ({ animation, width = 80, height = 80, loop = false, autoplay = true, className }: Props) => { +export const Animation = ({ variant, width = 80, height = 80, loop = false, autoplay = true, className }: Props) => { + const [animation, setAnimation] = useState(); + const defaultOptions = { loop, autoplay, @@ -25,5 +28,11 @@ export const Animation = ({ animation, width = 80, height = 80, loop = false, au const { View } = useLottie(defaultOptions, { width: width, height: height }); + useEffect(() => { + // using same animation repeatedly without deep clone lead to memory leak + // https://github.com/airbnb/lottie-web/issues/1159 + setAnimation(JSON.parse(JSON.stringify(Animations[variant]))); + }, [variant]); + return
{View}
; }; diff --git a/src/renderer/shared/ui/Buttons/Button/Button.test.tsx b/src/renderer/shared/ui/Buttons/Button/Button.test.tsx index 08fdcb8254..428333836d 100644 --- a/src/renderer/shared/ui/Buttons/Button/Button.test.tsx +++ b/src/renderer/shared/ui/Buttons/Button/Button.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import Button from './Button'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Buttons/Button', () => { test('should render component', () => { render( diff --git a/src/renderer/shared/ui/Checkbox/Checkbox.test.tsx b/src/renderer/shared/ui/Checkbox/Checkbox.test.tsx index de0232690d..98d16e931c 100644 --- a/src/renderer/shared/ui/Checkbox/Checkbox.test.tsx +++ b/src/renderer/shared/ui/Checkbox/Checkbox.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import Checkbox from './Checkbox'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Checkbox', () => { test('should render component', () => { render(test label); diff --git a/src/renderer/shared/ui/Countdown/Countdown.test.tsx b/src/renderer/shared/ui/Countdown/Countdown.test.tsx new file mode 100644 index 0000000000..9d8a01e0b6 --- /dev/null +++ b/src/renderer/shared/ui/Countdown/Countdown.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from '@testing-library/react'; + +import { Countdown } from './Countdown'; + +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + +jest.mock('@renderer/app/providers', () => ({ + useI18n: jest.fn().mockReturnValue({ + t: (key: string) => key, + }), +})); + +describe('ui/Countdown', () => { + test('should render component', () => { + render(); + + const title = screen.getByText('signing.qrCountdownTitle'); + expect(title).toBeInTheDocument(); + + const timer = screen.getByText('0:10'); + expect(timer).toBeInTheDocument(); + }); +}); diff --git a/src/renderer/shared/ui/Countdown/Countdown.tsx b/src/renderer/shared/ui/Countdown/Countdown.tsx new file mode 100644 index 0000000000..00ea2ce995 --- /dev/null +++ b/src/renderer/shared/ui/Countdown/Countdown.tsx @@ -0,0 +1,29 @@ +import { useI18n } from '@renderer/app/providers'; +import { cnTw, secondsToMinutes } from '@renderer/shared/lib/utils'; +import { CaptionText, FootnoteText } from '@renderer/shared/ui'; + +type Props = { + countdown: number; + className?: string; +}; + +export const Countdown = ({ countdown, className }: Props) => { + const { t } = useI18n(); + + return ( +
+ {t('signing.qrCountdownTitle')} + = 60 ? 'bg-label-background-green' : 'bg-label-background-red'), + )} + > + {/* if qr not loaded yet just show zero */} + {secondsToMinutes(countdown)} + +
+ ); +}; diff --git a/src/renderer/shared/ui/Counter/Counter.test.tsx b/src/renderer/shared/ui/Counter/Counter.test.tsx index 8a1d5313d5..c8daa7aeb4 100644 --- a/src/renderer/shared/ui/Counter/Counter.test.tsx +++ b/src/renderer/shared/ui/Counter/Counter.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import Counter from './Counter'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Counter', () => { test('should render component', () => { render(25); diff --git a/src/renderer/shared/ui/Dropdowns/Combobox/Combobox.test.tsx b/src/renderer/shared/ui/Dropdowns/Combobox/Combobox.test.tsx index 80cee9dbd4..f83666486a 100644 --- a/src/renderer/shared/ui/Dropdowns/Combobox/Combobox.test.tsx +++ b/src/renderer/shared/ui/Dropdowns/Combobox/Combobox.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import Combobox from './Combobox'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Combobox/Combobox', () => { const options = [ { id: '0', element: 'label_0', value: '0' }, diff --git a/src/renderer/shared/ui/Dropdowns/DropdownButton/DropdownButton.test.tsx b/src/renderer/shared/ui/Dropdowns/DropdownButton/DropdownButton.test.tsx index 6bebe23ff5..cb35dde94e 100644 --- a/src/renderer/shared/ui/Dropdowns/DropdownButton/DropdownButton.test.tsx +++ b/src/renderer/shared/ui/Dropdowns/DropdownButton/DropdownButton.test.tsx @@ -3,6 +3,15 @@ import noop from 'lodash/noop'; import DropdownButton, { ButtonDropdownOption } from './DropdownButton'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Dropdowns/DropdownButton', () => { const options: ButtonDropdownOption[] = [ { id: '0', title: 'label_0', iconName: 'globe', onClick: noop }, diff --git a/src/renderer/shared/ui/Dropdowns/MultiSelect/MultiSelect.test.tsx b/src/renderer/shared/ui/Dropdowns/MultiSelect/MultiSelect.test.tsx index c04e04d3b0..5f6e8ea509 100644 --- a/src/renderer/shared/ui/Dropdowns/MultiSelect/MultiSelect.test.tsx +++ b/src/renderer/shared/ui/Dropdowns/MultiSelect/MultiSelect.test.tsx @@ -2,6 +2,15 @@ import { act, render, screen } from '@testing-library/react'; import MultiSelect from './MultiSelect'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Dropdowns/MultiSelect', () => { const options = [ { id: '0', element: 'label_0', value: '0' }, diff --git a/src/renderer/shared/ui/Dropdowns/Select/Select.test.tsx b/src/renderer/shared/ui/Dropdowns/Select/Select.test.tsx index 5f81f04d84..bb344aa446 100644 --- a/src/renderer/shared/ui/Dropdowns/Select/Select.test.tsx +++ b/src/renderer/shared/ui/Dropdowns/Select/Select.test.tsx @@ -2,6 +2,15 @@ import { act, render, screen } from '@testing-library/react'; import Select from './Select'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Dropdowns/Select', () => { const options = [ { id: '0', element: 'label_0', value: '0' }, diff --git a/src/renderer/shared/ui/Icon/data/walletType.tsx b/src/renderer/shared/ui/Icon/data/walletType.tsx index 967435a36d..ccfe981753 100644 --- a/src/renderer/shared/ui/Icon/data/walletType.tsx +++ b/src/renderer/shared/ui/Icon/data/walletType.tsx @@ -2,16 +2,24 @@ import ParitySignerBgImg, { ReactComponent as ParitySignerBgSvg } from '@images/ import WatchOnlyBgImg, { ReactComponent as WatchOnlyBgSvg } from '@images/walletTypes/watchOnlyBg.svg'; import ParitySignerImg, { ReactComponent as ParitySignerSvg } from '@images/walletTypes/paritySigner.svg'; import WatchOnlyImg, { ReactComponent as WatchOnlySvg } from '@images/walletTypes/watchOnly.svg'; +import WatchOnlyOnboardingImg, { + ReactComponent as WatchOnlyOnboardingSvg, +} from '@images/walletTypes/watchOnlyOnboardiing.svg'; import MultisigBgImg, { ReactComponent as MultisigBgSvg } from '@images/walletTypes/multisigBg.svg'; import MultisigImg, { ReactComponent as MultisigSvg } from '@images/walletTypes/multisig.svg'; import VaultImg, { ReactComponent as VaultSvg } from '@images/walletTypes/vault.svg'; +import VaultOnboardingImg, { ReactComponent as VaultOnboardingSvg } from '@images/walletTypes/vaultOnboarding.svg'; import MultishardImg, { ReactComponent as MultishardSvg } from '@images/walletTypes/multishard.svg'; import NovaWalletImg, { ReactComponent as NovaWalletSvg } from '@images/walletTypes/novaWallet.svg'; -import WatchOnlyOnboardingImg, { - ReactComponent as WatchOnlyOnboardingSvg, -} from '@images/walletTypes/watchOnlyOnboardiing.svg'; import LedgerImg, { ReactComponent as LedgerSvg } from '@images/walletTypes/ledger.svg'; +import LedgerOnboardingImg, { ReactComponent as LedgerOnboardingSvg } from '@images/walletTypes/ledgerOnboarding.svg'; import WalletConnectImg, { ReactComponent as WalletConnectSvg } from '@images/walletTypes/walletConnect.svg'; +import WalletConnectOnboardingImg, { + ReactComponent as WalletConnectOnboardingSvg, +} from '@images/walletTypes/walletConnectOnboarding.svg'; +import NovaWalletOnboardingImg, { + ReactComponent as NovaWalletOnboardingSvg, +} from '@images/walletTypes/novaWalletOnboarding.svg'; const WalletTypeImages = { paritySigner: { svg: ParitySignerSvg, img: ParitySignerImg }, @@ -22,10 +30,14 @@ const WalletTypeImages = { multisigBg: { svg: MultisigBgSvg, img: MultisigBgImg }, multisig: { svg: MultisigSvg, img: MultisigImg }, vault: { svg: VaultSvg, img: VaultImg }, + vaultOnboarding: { svg: VaultOnboardingSvg, img: VaultOnboardingImg }, multishard: { svg: MultishardSvg, img: MultishardImg }, novaWallet: { img: NovaWalletImg, svg: NovaWalletSvg }, + novaWalletOnboarding: { img: NovaWalletOnboardingImg, svg: NovaWalletOnboardingSvg }, ledger: { img: LedgerImg, svg: LedgerSvg }, + ledgerOnboarding: { img: LedgerOnboardingImg, svg: LedgerOnboardingSvg }, walletConnect: { img: WalletConnectImg, svg: WalletConnectSvg }, + walletConnectOnboarding: { img: WalletConnectOnboardingImg, svg: WalletConnectOnboardingSvg }, } as const; export type WalletImages = keyof typeof WalletTypeImages; diff --git a/src/renderer/shared/ui/InfoLink/InfoLink.test.tsx b/src/renderer/shared/ui/InfoLink/InfoLink.test.tsx index 580a346aab..fefd1920fe 100644 --- a/src/renderer/shared/ui/InfoLink/InfoLink.test.tsx +++ b/src/renderer/shared/ui/InfoLink/InfoLink.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import InfoLink from './InfoLink'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('pages/Settings/InfoLink', () => { test('should render component', () => { render( diff --git a/src/renderer/shared/ui/LabelHelpbox/LabelHelpBox.test.tsx b/src/renderer/shared/ui/LabelHelpbox/LabelHelpBox.test.tsx index 3e2be11475..f6a6faaa54 100644 --- a/src/renderer/shared/ui/LabelHelpbox/LabelHelpBox.test.tsx +++ b/src/renderer/shared/ui/LabelHelpbox/LabelHelpBox.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import { LabelHelpBox } from './LabelHelpBox'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/LabelHelpBox', () => { test('should render component', () => { const label = 'This is simple content'; diff --git a/src/renderer/shared/ui/Loader/Loader.test.tsx b/src/renderer/shared/ui/Loader/Loader.test.tsx index 84e9bb8f86..32c0916b6e 100644 --- a/src/renderer/shared/ui/Loader/Loader.test.tsx +++ b/src/renderer/shared/ui/Loader/Loader.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import Loader from './Loader'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Loader', () => { test('should render component', () => { render(); diff --git a/src/renderer/shared/ui/Modals/BaseModal/BaseModal.test.tsx b/src/renderer/shared/ui/Modals/BaseModal/BaseModal.test.tsx index 407c46b6ff..3148b212f9 100644 --- a/src/renderer/shared/ui/Modals/BaseModal/BaseModal.test.tsx +++ b/src/renderer/shared/ui/Modals/BaseModal/BaseModal.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import BaseModal from './BaseModal'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Modals/BaseModal', () => { test('should render component', () => { render( diff --git a/src/renderer/shared/ui/Modals/ConfirmModal/ConfirmModal.test.tsx b/src/renderer/shared/ui/Modals/ConfirmModal/ConfirmModal.test.tsx index 5d4ba26a34..56c85b2967 100644 --- a/src/renderer/shared/ui/Modals/ConfirmModal/ConfirmModal.test.tsx +++ b/src/renderer/shared/ui/Modals/ConfirmModal/ConfirmModal.test.tsx @@ -3,6 +3,15 @@ import noop from 'lodash/noop'; import ConfirmModal from './ConfirmModal'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Modals/ConfirmModal', () => { const defaultProps = { isOpen: true, diff --git a/src/renderer/shared/ui/Modals/StatusModal/StatusModal.tsx b/src/renderer/shared/ui/Modals/StatusModal/StatusModal.tsx new file mode 100644 index 0000000000..25dbe7035d --- /dev/null +++ b/src/renderer/shared/ui/Modals/StatusModal/StatusModal.tsx @@ -0,0 +1,59 @@ +import { Fragment, PropsWithChildren, ReactNode } from 'react'; +import { Dialog, Transition } from '@headlessui/react'; + +import { ModalBackdrop, ModalTransition } from '@renderer/shared/ui/Modals/common'; +import { FootnoteText, SmallTitleText } from '@renderer/shared/ui'; +import { cnTw } from '@renderer/shared/lib/utils'; + +type Props = { + content?: ReactNode; + title: string; + description?: string; + isOpen: boolean; + onClose: () => void; + className?: string; +}; + +export const StatusModal = ({ + title, + description, + isOpen, + content, + className, + children, + onClose, +}: PropsWithChildren) => { + return ( + + + + +
+ + + {content} + + + {title} + + + {description && ( + + {description} + + )} + + {children &&
{children}
} +
+
+
+
+
+ ); +}; diff --git a/src/renderer/shared/ui/PopoverLink/PopoverLink.test.tsx b/src/renderer/shared/ui/PopoverLink/PopoverLink.test.tsx index 93aa86dc71..554f31a888 100644 --- a/src/renderer/shared/ui/PopoverLink/PopoverLink.test.tsx +++ b/src/renderer/shared/ui/PopoverLink/PopoverLink.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import PopoverLink from './PopoverLink'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('pages/Settings/PopoverLink', () => { test('should render component', () => { render( diff --git a/src/renderer/shared/ui/Popovers/InfoPopover/InfoPopover.test.tsx b/src/renderer/shared/ui/Popovers/InfoPopover/InfoPopover.test.tsx index 341b667259..a5ba4bccb9 100644 --- a/src/renderer/shared/ui/Popovers/InfoPopover/InfoPopover.test.tsx +++ b/src/renderer/shared/ui/Popovers/InfoPopover/InfoPopover.test.tsx @@ -2,6 +2,15 @@ import { act, render, screen } from '@testing-library/react'; import InfoPopover, { InfoSection } from './InfoPopover'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + const menuLinks = [ { id: '3', diff --git a/src/renderer/shared/ui/Popovers/Tooltip/Tooltip.test.tsx b/src/renderer/shared/ui/Popovers/Tooltip/Tooltip.test.tsx index 8db46d839c..c0d2ff3e04 100644 --- a/src/renderer/shared/ui/Popovers/Tooltip/Tooltip.test.tsx +++ b/src/renderer/shared/ui/Popovers/Tooltip/Tooltip.test.tsx @@ -3,6 +3,15 @@ import userEvent from '@testing-library/user-event'; import { Tooltip } from './Tooltip'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Popover', () => { test('should render component', () => { render(Hover me); diff --git a/src/renderer/shared/ui/RadioGroup/RadioGroup.test.tsx b/src/renderer/shared/ui/RadioGroup/RadioGroup.test.tsx index b82838a4ff..7717123c3a 100644 --- a/src/renderer/shared/ui/RadioGroup/RadioGroup.test.tsx +++ b/src/renderer/shared/ui/RadioGroup/RadioGroup.test.tsx @@ -2,6 +2,15 @@ import { act, render, screen } from '@testing-library/react'; import RadioGroup from './RadioGroup'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/RadioGroup', () => { const options = [ { id: '1', value: 1, title: 'Test 1' }, diff --git a/src/renderer/shared/ui/StatusLabel/StatusLabel.test.tsx b/src/renderer/shared/ui/StatusLabel/StatusLabel.test.tsx index 115b38f976..72966e7c21 100644 --- a/src/renderer/shared/ui/StatusLabel/StatusLabel.test.tsx +++ b/src/renderer/shared/ui/StatusLabel/StatusLabel.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import StatusLabel from './StatusLabel'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/StatusLabel', () => { test('should render component', () => { render(); diff --git a/src/renderer/shared/ui/Switch/Switch.test.tsx b/src/renderer/shared/ui/Switch/Switch.test.tsx index e16975ea09..53fa1d0426 100644 --- a/src/renderer/shared/ui/Switch/Switch.test.tsx +++ b/src/renderer/shared/ui/Switch/Switch.test.tsx @@ -2,6 +2,15 @@ import { render, screen } from '@testing-library/react'; import Switch from './Switch'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + describe('ui/Switch', () => { test('should render component', () => { render(test label); diff --git a/src/renderer/shared/ui/Tabs/Tabs.test.tsx b/src/renderer/shared/ui/Tabs/Tabs.test.tsx index c5d4837d13..5722b88ce9 100644 --- a/src/renderer/shared/ui/Tabs/Tabs.test.tsx +++ b/src/renderer/shared/ui/Tabs/Tabs.test.tsx @@ -4,6 +4,15 @@ import userEvent from '@testing-library/user-event'; import { Tabs } from './Tabs'; import { TabItem } from './common/types'; +jest.mock('@renderer/entities/walletConnect', () => ({ + walletConnectModel: { events: {} }, + DEFAULT_POLKADOT_METHODS: {}, + getWalletConnectChains: jest.fn(), +})); +jest.mock('@renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model', () => ({ + wcOnboardingModel: { events: {} }, +})); + const tabItems: TabItem[] = [ { id: '1', title: 'Tab 1 title', panel:
tab 1 content
}, { id: '2', title: 'Tab 2 title', panel:
tab 2 content
}, diff --git a/src/renderer/shared/ui/index.ts b/src/renderer/shared/ui/index.ts index 2e87620226..1b05adb769 100644 --- a/src/renderer/shared/ui/index.ts +++ b/src/renderer/shared/ui/index.ts @@ -8,6 +8,7 @@ import ButtonLink from './Buttons/ButtonLink/ButtonLink'; import ButtonBack from './Buttons/ButtonBack/ButtonBack'; import BaseModal from './Modals/BaseModal/BaseModal'; import ConfirmModal from './Modals/ConfirmModal/ConfirmModal'; +import { StatusModal } from './Modals/StatusModal/StatusModal'; import InfoPopover from './Popovers/InfoPopover/InfoPopover'; import { Popover } from './Popovers/Popover/Popover'; import InfoLink from './InfoLink/InfoLink'; @@ -52,6 +53,7 @@ import Duration from './Duration/Duration'; import Loader from './Loader/Loader'; import DetailRow from './DetailRow/DetailRow'; import { Truncate } from './Truncate/Truncate'; +import { Countdown } from './Countdown/Countdown'; // FIXME: Animation component exported separately. // Adding them to this file causes to crash all tests which use anything from that file @@ -71,6 +73,7 @@ export { IconButton, BaseModal, ConfirmModal, + StatusModal, InfoPopover, MenuPopover, Popover, @@ -111,4 +114,5 @@ export { DetailRow, Truncate, MainLayout, + Countdown, }; diff --git a/src/renderer/widgets/CreateWallet/model/wallet-provider-model.ts b/src/renderer/widgets/CreateWallet/model/wallet-provider-model.ts index a78dd56461..d8c0978987 100644 --- a/src/renderer/widgets/CreateWallet/model/wallet-provider-model.ts +++ b/src/renderer/widgets/CreateWallet/model/wallet-provider-model.ts @@ -7,6 +7,7 @@ const walletTypeSet = createEvent(); const modalClosed = createEvent(); const storeCleared = createEvent(); const completed = createEvent(); +const rejected = createEvent(); const $walletType = createStore(null).reset([modalClosed, completed]); @@ -38,6 +39,7 @@ export const walletProviderModel = { walletTypeSet, modalClosed, completed, + rejected, navigateApiChanged: navigationApi.navigateApiChanged, }, }; diff --git a/src/renderer/widgets/CreateWallet/ui/CreateWalletProvider.tsx b/src/renderer/widgets/CreateWallet/ui/CreateWalletProvider.tsx index 5d7bb8266c..6033220b39 100644 --- a/src/renderer/widgets/CreateWallet/ui/CreateWalletProvider.tsx +++ b/src/renderer/widgets/CreateWallet/ui/CreateWalletProvider.tsx @@ -5,6 +5,8 @@ import { useNavigate } from 'react-router-dom'; import { walletProviderModel } from '../model/wallet-provider-model'; import WatchOnly from '@renderer/pages/Onboarding/WatchOnly/WatchOnly'; import Vault from '@renderer/pages/Onboarding/Vault/Vault'; +import { NovaWallet } from '@renderer/pages/Onboarding/WalletConnect/NovaWallet'; +import { WalletConnect } from '@renderer/pages/Onboarding/WalletConnect/WalletConnect'; import { MultisigAccount } from './MultisigAccount/MultisigAccount'; import { WalletType } from '@renderer/shared/core'; import { Paths } from '@renderer/shared/routes'; @@ -20,6 +22,8 @@ const WalletModals: Record JSX.Element> = { [WalletType.MULTISHARD_PARITY_SIGNER]: (props) => , [WalletType.SINGLE_PARITY_SIGNER]: (props) => , [WalletType.MULTISIG]: (props) => , + [WalletType.WALLET_CONNECT]: (props) => , + [WalletType.NOVA_WALLET]: (props) => , }; export const CreateWalletProvider = () => { diff --git a/src/renderer/widgets/SendAssetModal/ui/components/ActionSteps/Confirmation.tsx b/src/renderer/widgets/SendAssetModal/ui/components/ActionSteps/Confirmation.tsx index ccf00d4a16..a250e5a7e5 100644 --- a/src/renderer/widgets/SendAssetModal/ui/components/ActionSteps/Confirmation.tsx +++ b/src/renderer/widgets/SendAssetModal/ui/components/ActionSteps/Confirmation.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { useUnit } from 'effector-react'; import { Transaction, DepositWithLabel, Fee, XcmTypes } from '@renderer/entities/transaction'; import { TransactionAmount } from '@renderer/pages/Operations/components/TransactionAmount'; @@ -7,8 +8,11 @@ import { ExtendedChain } from '@renderer/entities/network'; import { useI18n } from '@renderer/app/providers'; import { XcmFee } from '@renderer/entities/transaction/ui/XcmFee/XcmFee'; import { AssetXCM, XcmConfig } from '@renderer/shared/api/xcm'; +import { SignButton } from '@renderer/entities/operation/ui/SignButton'; +import { WalletType } from '@renderer/shared/core'; import type { Account, MultisigAccount } from '@renderer/shared/core'; import Details from '../Details'; +import { walletModel } from '@renderer/entities/wallet'; type Props = { transaction: Transaction; @@ -34,6 +38,7 @@ export const Confirmation = ({ onBack, }: Props) => { const { t } = useI18n(); + const activeWallet = useUnit(walletModel.$activeWallet); const [feeLoaded, setFeeLoaded] = useState(false); const isXcmTransfer = XcmTypes.includes(transaction?.type); @@ -98,9 +103,11 @@ export const Confirmation = ({ {t('operation.goBackButton')} - +
); diff --git a/src/shared/locale/en.json b/src/shared/locale/en.json index 61f690ead9..7d50587a2d 100644 --- a/src/shared/locale/en.json +++ b/src/shared/locale/en.json @@ -192,6 +192,10 @@ "checkAccountsButton": "Check your accounts", "confirmAccountsListButton": "Yes, these are my accounts", "continueButton": "Continue", + "novaWallet": { + "scanTitle": "Scan with Nova Wallet", + "title": "Add wallet by Nova Wallet" + }, "paritySigner": { "accessDeniedDescription": "Please make sure you grant access to camera", "accessDeniedLabel": "Access denied!", @@ -252,6 +256,16 @@ "scanTitle": "Scan the account QR code", "title": "Add wallet by Polkadot Vault" }, + "walletConnect": { + "accountsTitle": "Manage accounts", + "addressColumn": "Address", + "manageTitle": "Set up your wallet", + "nameColumn": "Account name", + "pairedDescription": "Paired", + "rejected": "Pairing rejected", + "scanTitle": "Scan with your mobile wallet", + "title": "Add wallet by Wallet Connect" + }, "walletNameExample": "Name examples: Main account, My validator, Dotsama crowdloans, etc", "walletNameLabel": "Wallet name", "walletNamePlaceholder": "Enter a name for your wallet", @@ -336,7 +350,11 @@ "selectAccount": "Select account", "selectSignatory": "Select signatory", "selectSignatoryTitle": "Select signatory", - "signButton": "Sign with Polkadot Vault", + "sign": { + "novaWallet": "Sign with Nova Wallet", + "polkadotVault": "Sign with Polkadot Vault", + "walletConnect": "Sign with WalletConnect" + }, "signatoriesTitle": "Signatories", "signing": "{ signed } of { threshold } signed", "status": { @@ -348,6 +366,21 @@ }, "successMessage": "Operation Approved", "successRejectMessage": "Operation Rejected", + "walletConnect": { + "defaultWalletName": "WalletConnect", + "expiredDescription": "The operation isn’t valid anymore", + "reconnect": { + "cancelButton": "Cancel", + "confirmButton": "Reconnect", + "connected": "Connected", + "description": "You need to reconnect to proceed with signing", + "reconnecting": "Waiting for Reconnect...", + "title": "{ walletName } is not connected" + }, + "rejected": "Rejected", + "signTitle": "Confirm the operation on your { walletName } app", + "tryAgainButton": "Try again" + }, "xcmFee": "Cross-chain fee" }, "operations": { @@ -553,7 +586,7 @@ "metadataPortalLink": "Update metadata in Polkadot Vault", "parsingCount": "{current} of {total}", "parsingLabel": "Parsing data", - "qrCountdownTitle": "QR code is valid for", + "qrCountdownTitle": "Time remaining", "qrNotValid": "QR code isn’t valid anymore", "scanQrTitle": "Scan the operation QR code with Polkadot Vault", "scanSignatureTitle": "Scan QR code from Polkadot Vault", @@ -637,7 +670,6 @@ "queueButton": "Add to operation queue", "restakeRewards": "Restake rewards", "rewardsDestinationLabel": "Rewards destination", - "signButton": "Sign with Polkadot Vault", "signatoryLabel": "Signatory", "signer": "Signer", "submittingOperation": "Submitting operation", @@ -837,14 +869,18 @@ "wallets": { "addButtonTitle": "Add", "addMultisig": "Multisig", + "addNovaWallet": "Nova Wallet", "addPolkadotVault": "Polkadot Vault", + "addWalletConnect": "Wallet Connect", "addWatchOnly": "Watch-only", "multishardLabel": "Multishard", "multisigLabel": "Multisig", + "novaWalletLabel": "Nova Wallet", "paritySignerLabel": "Polkadot Vault", "searchPlaceholder": "Search", "shards": "Shards", "title": "Wallets", + "walletConnectLabel": "Wallet Connect", "watchOnlyLabel": "Watch-only" }, "welcome": { diff --git a/src/shared/locale/ru.json b/src/shared/locale/ru.json index 3dea3106fb..d2eab9959b 100644 --- a/src/shared/locale/ru.json +++ b/src/shared/locale/ru.json @@ -192,6 +192,10 @@ "checkAccountsButton": "Ваши аккаунты", "confirmAccountsListButton": "Да, это мои аккаунты", "continueButton": "Continue", + "novaWallet": { + "scanTitle": "Scan with Nova Wallet", + "title": "Pair Nova Wallet" + }, "paritySigner": { "accessDeniedDescription": "Убедитесь, что доступ к камере разрешен", "accessDeniedLabel": "Доступ запрещен!", @@ -252,6 +256,16 @@ "scanTitle": "Scan the account QR code", "title": "Add wallet by Polkadot Vault" }, + "walletConnect": { + "accountsTitle": "Manage accounts", + "addressColumn": "Address", + "manageTitle": "Set up your wallet", + "nameColumn": "Account name", + "pairedDescription": "Paired", + "rejected": "Pairing rejected", + "scanTitle": "Scan with your mobile wallet", + "title": "Pair via Wallet Connect" + }, "walletNameExample": "Например: Основной аккаунт, My validator, Dotsama crowdloans, и т.д.", "walletNameLabel": "Название кошелька", "walletNamePlaceholder": "Введите название кошелька", @@ -336,7 +350,11 @@ "selectAccount": "Select account", "selectSignatory": "Выберите подписанта", "selectSignatoryTitle": "Выберите подписанта", - "signButton": "Подписать в Polkadot Vault", + "sign": { + "novaWallet": "Sign with Nova Wallet", + "polkadotVault": "Sign with Polkadot Vault", + "walletConnect": "Sign with WalletConnect" + }, "signatoriesTitle": "Подписи", "signing": "{ signed } из { threshold } подписали", "status": { @@ -348,6 +366,21 @@ }, "successMessage": "Operation Approved", "successRejectMessage": "Операция Отменена", + "walletConnect": { + "defaultWalletName": "WalletConnect", + "expiredDescription": "The operation isn’t valid anymore", + "reconnect": { + "cancelButton": "Cancel", + "confirmButton": "Reconnect", + "connected": "Connected", + "description": "You need to reconnect to proceed with signing", + "reconnecting": "Waiting for Reconnect...", + "title": "{ walletName } is not connected" + }, + "rejected": "Rejected", + "signTitle": "Confirm the operation on your { walletName } app", + "tryAgainButton": "Try again" + }, "xcmFee": "Cross-chain fee" }, "operations": { @@ -637,7 +670,6 @@ "queueButton": "добавить в очередь", "restakeRewards": "Вложить награды", "rewardsDestinationLabel": "Выплата наград", - "signButton": "Подписать в Polkadot Vault", "signatoryLabel": "Подписант", "signer": "Подписант", "submittingOperation": "Подтверждение операции", @@ -837,14 +869,18 @@ "wallets": { "addButtonTitle": "Добавить", "addMultisig": "Multisig", + "addNovaWallet": "Nova Wallet", "addPolkadotVault": "Polkadot Vault", + "addWalletConnect": "Wallet Connect", "addWatchOnly": "Watch-only", "multishardLabel": "Multishard", "multisigLabel": "Multisig", + "novaWalletLabel": "Nova Wallet", "paritySignerLabel": "Polkadot Vault", "searchPlaceholder": "Поиск", "shards": "Shards", "title": "Кошельки", + "walletConnectLabel": "Wallet Connect", "watchOnlyLabel": "Watch-only" }, "welcome": {