diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17349025f..77a33de74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,62 +1,74 @@ -# Each PR will build preview site that help to check code is work as expect. - name: Build on: - pull_request: - types: [opened, synchronize, reopened] push: + branches: + # - develop + - feat/for_desktop + - feat/for_desktop* tags: - '*' +defaults: + run: + shell: bash -leo pipefail {0} + jobs: # Prepare node modules. Reuse cache if available setup: name: prepare build - runs-on: ubuntu-latest + runs-on: [self-hosted, X64, Linux, builder] env: NODE_OPTIONS: '--max_old_space_size=4096' steps: - name: checkout uses: actions/checkout@v3 - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: '16.14' - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - - - uses: actions/cache@v3 - id: yarn-node_modules # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: node_modules - key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} - - - name: Get Yarn Cache - if: steps.yarn-cache.outputs.cache-hit == 'true' - run: yarn --prefer-offline + - name: Env Test + id: env-test + run: | + echo "whoami $(whoami)" + echo "shell is $(echo $0)" + echo "which node $(which node)" + + # - name: Setup node + # uses: actions/setup-node@v3 + # with: + # node-version: '16.14' + # cache: 'yarn' + + # - name: Get yarn cache directory path + # id: yarn-cache-dir-path + # run: echo "::set-output name=dir::$(yarn cache dir)" + + # - uses: actions/cache@v3 + # id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + # with: + # path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + # - uses: actions/cache@v3 + # id: yarn-node_modules # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + # with: + # path: node_modules + # key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} + + # - name: Get Yarn Cache + # if: steps.yarn-cache.outputs.cache-hit == 'true' + # run: yarn --prefer-offline - name: Use NPM Token with organization read access uses: heisenberg-2077/use-npm-token-action@v1 with: token: '${{ secrets.NPM_AUTH_TOKEN }}' - - name: Install Dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - run: yarn install --frozen-lockfile + # - name: Install Dependencies + # if: steps.yarn-cache.outputs.cache-hit != 'true' + # run: yarn install --frozen-lockfile build-pro: name: build pro - runs-on: ubuntu-latest + runs-on: [self-hosted, X64, Linux, builder] needs: setup env: NODE_OPTIONS: '--max_old_space_size=4096' @@ -81,65 +93,76 @@ jobs: key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} - name: build - run: yarn build:pro - - - name: Upload artifact - uses: actions/upload-artifact@v3 - if: ${{ github.event_name == 'pull_request' }} - with: - name: Rabby_${{github.sha}} - path: dist - retention-days: 7 - - - name: Upload artifact - uses: actions/upload-artifact@v3 - if: ${{ github.event_name == 'push' }} - with: - name: Rabby_${{github.ref_name}} - path: dist - retention-days: 7 - - build-debug: - name: build debug - runs-on: ubuntu-latest - needs: setup - env: - NODE_OPTIONS: '--max_old_space_size=4096' - steps: - - name: checkout - uses: actions/checkout@v3 - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - - - uses: actions/cache@v3 - id: yarn-node_modules # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: node_modules - key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} - - - name: build - run: yarn build:debug - - - name: Upload artifact - uses: actions/upload-artifact@v3 - if: ${{ github.event_name == 'pull_request' }} - with: - name: Rabby_${{github.sha}}_debug - path: dist - retention-days: 7 - - - name: Upload artifact - uses: actions/upload-artifact@v3 - if: ${{ github.event_name == 'push' }} - with: - name: Rabby_${{github.ref_name}}_debug - path: dist - retention-days: 7 + run: | + sh ./scripts/pack.sh; + env: + RABBY_BUILD_BUCKET: ${{ secrets.RABBY_BUILD_BUCKET }} + LARK_CHAT_URL: ${{ secrets.LARK_CHAT_URL }} + LARK_CHAT_SECRET: ${{ secrets.LARK_CHAT_SECRET }} + # see more details on https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + ACTIONS_JOB_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GIT_COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} + GIT_REF_NAME: ${{ github.ref_name }} + GIT_REF_URL: ${{ github.server_url }}/${{ github.repository }}/tree/${{ github.ref_name }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + # - name: Upload artifact + # uses: actions/upload-artifact@v3 + # if: ${{ github.event_name == 'pull_request' }} + # with: + # name: Rabby_${{github.sha}} + # path: dist + # retention-days: 7 + + # - name: Upload artifact + # uses: actions/upload-artifact@v3 + # if: ${{ github.event_name == 'push' }} + # with: + # name: Rabby_${{github.ref_name}} + # path: dist + # retention-days: 7 + + # build-debug: + # name: build debug + # runs-on: [self-hosted, X64, Linux, builder] + # needs: setup + # env: + # NODE_OPTIONS: '--max_old_space_size=4096' + # steps: + # - name: checkout + # uses: actions/checkout@v3 + + # - name: Get yarn cache directory path + # id: yarn-cache-dir-path + # run: echo "::set-output name=dir::$(yarn cache dir)" + + # - uses: actions/cache@v3 + # id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + # with: + # path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + # - uses: actions/cache@v3 + # id: yarn-node_modules # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + # with: + # path: node_modules + # key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} + + # - name: build + # run: yarn build:debug + + # - name: Upload artifact + # uses: actions/upload-artifact@v3 + # if: ${{ github.event_name == 'pull_request' }} + # with: + # name: Rabby_${{github.sha}}_debug + # path: dist + # retention-days: 7 + + # - name: Upload artifact + # uses: actions/upload-artifact@v3 + # if: ${{ github.event_name == 'push' }} + # with: + # name: Rabby_${{github.ref_name}}_debug + # path: dist + # retention-days: 7 diff --git a/.github/workflows/build_debug.yml b/.github/workflows/build_debug.yml new file mode 100644 index 000000000..f693c02f3 --- /dev/null +++ b/.github/workflows/build_debug.yml @@ -0,0 +1,95 @@ +# Each PR will build debug that help to check if build works as expect. + +name: Test Build + +on: + pull_request: + types: [opened, synchronize, reopened] + +defaults: + run: + shell: bash -leo pipefail {0} + +jobs: + # Prepare node modules. Reuse cache if available + setup: + name: prepare build + runs-on: [self-hosted, X64, Linux, builder] + env: + NODE_OPTIONS: '--max_old_space_size=4096' + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: Env Test + id: env-test + run: | + echo "whoami $(whoami)" + echo "shell is $(echo $0)" + echo "which node $(which node)" + + # - name: Setup node + # uses: actions/setup-node@v3 + # with: + # node-version: '16.14' + # cache: 'yarn' + + # - name: Get yarn cache directory path + # id: yarn-cache-dir-path + # run: echo "::set-output name=dir::$(yarn cache dir)" + + # - uses: actions/cache@v3 + # id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + # with: + # path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + # - uses: actions/cache@v3 + # id: yarn-node_modules # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + # with: + # path: node_modules + # key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} + + # - name: Get Yarn Cache + # if: steps.yarn-cache.outputs.cache-hit == 'true' + # run: yarn --prefer-offline + + - name: Use NPM Token with organization read access + uses: heisenberg-2077/use-npm-token-action@v1 + with: + token: '${{ secrets.NPM_AUTH_TOKEN }}' + + # - name: Install Dependencies + # if: steps.yarn-cache.outputs.cache-hit != 'true' + # run: yarn install --frozen-lockfile + + build-debug: + name: build debug + runs-on: [self-hosted, X64, Linux, builder] + needs: setup + env: + NODE_OPTIONS: '--max_old_space_size=4096' + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v3 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + - uses: actions/cache@v3 + id: yarn-node_modules # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: node_modules + key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: build + run: | + export NO_UPLOAD=1; + sh ./scripts/pack.sh; \ No newline at end of file diff --git a/.github/workflows/private.yaml b/.github/workflows/private.yaml index 1d6602111..60283e779 100644 --- a/.github/workflows/private.yaml +++ b/.github/workflows/private.yaml @@ -1,13 +1,16 @@ name: Triger Private Build on: - pull_request: - types: [opened, synchronize, reopened] + # pull_request: + # types: [opened, synchronize, reopened] push: branches: - - develop - tags: - - v* + - dont_trigger + # - develop + # - feat/for_desktop + # - feat/for_desktop* + # tags: + # - v* jobs: create-new-private-build: diff --git a/.gitignore b/.gitignore index 0c35403d6..770550b0d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ dist dist-* .yarn .DS_Store -yarn-error \ No newline at end of file +yarn-error + +tmp \ No newline at end of file diff --git a/_raw/vendor/matomo.js b/_raw/vendor/matomo.js index 4a9e210ab..d5e1e98c9 100644 --- a/_raw/vendor/matomo.js +++ b/_raw/vendor/matomo.js @@ -7,7 +7,7 @@ _paq.push(['enableLinkTracking']); chrome.storage.local.get('extensionId', function (result) { var u = 'https://matomo.debank.com/'; _paq.push(['setTrackerUrl', u + 'matomo.php']); - _paq.push(['setSiteId', '2']); + _paq.push(['setSiteId', '4']); if (result.extensionId) { _paq.push(['setVisitorId', result.extensionId]); } diff --git a/build/paths.js b/build/paths.js index c3c56b9d6..19db50f7c 100644 --- a/build/paths.js +++ b/build/paths.js @@ -1,18 +1,26 @@ const path = require('path'); const fs = require('fs'); -const appRoot = fs.realpathSync(process.cwd()); +const appRoot = fs.realpathSync(path.resolve(__dirname, '..')); + +let desktopRepo = process.env.RABBY_DESKTOP_REPO || path.join(appRoot, '../RabbyDesktop'); +if (!fs.existsSync(desktopRepo)) { + console.log('RabbyDesktop repo not found at ' + desktopRepo); + desktopRepo = null; +} else { + console.log('Using RabbyDesktop repo at ' + desktopRepo); +}; const rootResolve = path.resolve.bind(path, appRoot); module.exports = { root: appRoot, src: rootResolve('src'), - popupHtml: rootResolve('src/ui/popup.html'), + popupHtml: rootResolve('src/ui/popup.ejs'), notificationHtml: rootResolve('src/ui/notification.html'), indexHtml: rootResolve('src/ui/index.html'), backgroundHtml: rootResolve('src/background/background.html'), - dist: rootResolve('dist'), + dist: desktopRepo ? path.resolve(desktopRepo, './assets/chrome_exts/rabby') : rootResolve('dist'), rootResolve, } diff --git a/build/webpack.common.config.js b/build/webpack.common.config.js index b33de900c..2764e8cd6 100644 --- a/build/webpack.common.config.js +++ b/build/webpack.common.config.js @@ -39,6 +39,11 @@ const config = { sideEffects: true, test: /[\\/]pageProvider[\\/]index.ts/, loader: 'ts-loader', + options: { + compilerOptions: { + outDir: paths.dist, + }, + } }, { test: /[\\/]ui[\\/]index.tsx/, @@ -58,6 +63,7 @@ const config = { }), compilerOptions: { module: 'es2015', + outDir: paths.dist, }, }, }, @@ -96,6 +102,9 @@ const config = { }), ], }), + compilerOptions: { + outDir: paths.dist, + } }, }, ], @@ -178,9 +187,9 @@ const config = { ], }, plugins: [ - new ESLintWebpackPlugin({ - extensions: ['ts', 'tsx', 'js', 'jsx'], - }), + // new ESLintWebpackPlugin({ + // extensions: ['ts', 'tsx', 'js', 'jsx'], + // }), // new AntdDayjsWebpackPlugin(), new HtmlWebpackPlugin({ inject: true, diff --git a/package.json b/package.json index 864bad410..fdcd6fe01 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,19 @@ "description": "A browser plugin for DeFi users", "scripts": { "postinstall": "patch-package", - "clean": "mkdir -p dist && rm -rf dist/* && cp -r _raw/* dist", + "clean": "sh ./scripts/clean-assets.sh", "make-vars": "node scripts/make-vars.js", - "build:dev": "npm run clean && npm run make-vars && TAILWIND_MODE=watch webpack --progress --env config=dev", - "build:pro": "npm run clean && npm run make-vars && webpack --progress --env config=pro", - "build:debug": "npm run clean && npm run make-vars && webpack --progress --env config=debug", - "build:sourcemap": "npm run clean && npm run make-vars && webpack --progress --env config=sourcemap", + "build:dev": "npm run clean && TAILWIND_MODE=watch webpack --progress --env config=dev", + "build:pro": "npm run clean && webpack --progress --env config=pro", + "build:debug": "npm run clean && webpack --progress --env config=debug", + "build:sourcemap": "npm run clean && webpack --progress --env config=sourcemap", "lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx ./src", "test": "jest", "pub": "node build/release.js" }, "dependencies": { "@debank/common": "^0.2.34", - "@debank/festats": "1.0.0", + "@debank/festats": "1.0.3", "@debank/rabby-api": "^0.5.27", "@debank/rabby-security-engine": "^1.0.14", "@dnd-kit/core": "^5.0.1", diff --git a/patches/@rabby-wallet+eth-trezor-keyring+2.2.0.patch b/patches/@rabby-wallet+eth-trezor-keyring+2.2.0.patch new file mode 100644 index 000000000..ddda98fe9 --- /dev/null +++ b/patches/@rabby-wallet+eth-trezor-keyring+2.2.0.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@rabby-wallet/eth-trezor-keyring/dist/index.js b/node_modules/@rabby-wallet/eth-trezor-keyring/dist/index.js +index a4e5f22..7dc62fc 100644 +--- a/node_modules/@rabby-wallet/eth-trezor-keyring/dist/index.js ++++ b/node_modules/@rabby-wallet/eth-trezor-keyring/dist/index.js +@@ -127,7 +127,7 @@ class TrezorKeyring extends events_1.EventEmitter { + } + }); + if (!this.trezorConnectInitiated) { +- connect_web_1.default.init({ manifest: TREZOR_CONNECT_MANIFEST, lazyLoad: true }); ++ connect_web_1.default.init({ manifest: TREZOR_CONNECT_MANIFEST, webusb: false, lazyLoad: true }); + this.trezorConnectInitiated = true; + } + } diff --git a/patches/@rabby-wallet+page-provider+0.1.15.patch b/patches/@rabby-wallet+page-provider+0.1.15.patch new file mode 100644 index 000000000..38b937dd0 --- /dev/null +++ b/patches/@rabby-wallet+page-provider+0.1.15.patch @@ -0,0 +1,20 @@ +diff --git a/node_modules/@rabby-wallet/page-provider/dist/index.js b/node_modules/@rabby-wallet/page-provider/dist/index.js +index b6d3c15..cee0923 100644 +--- a/node_modules/@rabby-wallet/page-provider/dist/index.js ++++ b/node_modules/@rabby-wallet/page-provider/dist/index.js +@@ -559,6 +559,15 @@ class EthereumProvider extends events.EventEmitter { + }; + // TODO: support multi request! + this.request = async (data) => { ++ if (typeof window.__rbCheckRequestable !== 'function') { ++ throw new Error(ethRpcErrors.ethErrors.rpc.invalidRequest()); ++ } ++ ++ const checked = await window.__rbCheckRequestable(data); ++ if (!checked) { ++ return ; ++ } ++ + if (!this._isReady) { + const promise = new Promise((resolve, reject) => { + this._cacheRequestsBeforeReady.push({ diff --git a/patches/@trezor+connect-web+9.0.6.patch b/patches/@trezor+connect-web+9.0.6.patch new file mode 100644 index 000000000..5f3065f78 --- /dev/null +++ b/patches/@trezor+connect-web+9.0.6.patch @@ -0,0 +1,25 @@ +diff --git a/node_modules/@trezor/connect-web/lib/popup/index.js b/node_modules/@trezor/connect-web/lib/popup/index.js +index a323aaa..0bc361f 100644 +--- a/node_modules/@trezor/connect-web/lib/popup/index.js ++++ b/node_modules/@trezor/connect-web/lib/popup/index.js +@@ -108,11 +108,15 @@ class PopupManager extends events_1.default { + currentWindow: true, + active: true, + }, tabs => { +- this.extensionTabId = tabs[0].id; +- chrome.tabs.create({ +- url, +- index: tabs[0].index + 1, +- }, tab => { ++ var params = { ++ url: url ++ }; ++ if (Array.isArray(tabs) && tabs.length > 0) { ++ this.extensionTabId = tabs[0].id; ++ params.index = tabs[0].index + 1; ++ } ++ ++ chrome.tabs.create(params, (tab) => { + this._window = tab; + }); + }); diff --git a/scripts/clean-assets.sh b/scripts/clean-assets.sh new file mode 100644 index 000000000..c7c1e2151 --- /dev/null +++ b/scripts/clean-assets.sh @@ -0,0 +1,18 @@ +script_dir="$( cd "$( dirname "$0" )" && pwd )" +project_dir=$(dirname "$script_dir") + +if [ -z $RABBY_DESKTOP_REPO ]; then + if [ -d $project_dir/../RabbyDesktop ]; then + export RABBY_DESKTOP_REPO=$( cd "$project_dir/../RabbyDesktop" && pwd ) + echo "[clean-assets] RABBY_DESKTOP_REPO is not set, use default: $RABBY_DESKTOP_REPO" + fi +else + echo "[clean-assets] RABBY_DESKTOP_REPO is set to: $RABBY_DESKTOP_REPO" +fi + +if [ ! -z $RABBY_DESKTOP_REPO ]; then + mkdir -p $RABBY_DESKTOP_REPO/assets/chrome_exts/rabby && rm -rf $RABBY_DESKTOP_REPO/assets/chrome_exts/rabby/* && cp -r _raw/* $RABBY_DESKTOP_REPO/assets/chrome_exts/rabby +else + echo "[clean-assets] RABBY_DESKTOP_REPO is not set, skip clean assets." + rm -rf $project_dir/dist && mkdir -p $project_dir/dist && cp -r _raw/* $project_dir/dist +fi \ No newline at end of file diff --git a/scripts/notify-lark.js b/scripts/notify-lark.js new file mode 100644 index 000000000..90019084c --- /dev/null +++ b/scripts/notify-lark.js @@ -0,0 +1,112 @@ +#!/usr/bin/env node + +// curl -X POST -H "Content-Type: application/json" \ +// -d '{"msg_type":"text","content":{"text":"request example"}}' +const { createHmac } = require('crypto'); +const Axios = require('axios'); + +function makeSign(secret) { + const timestamp = Date.now(); + const timeSec = Math.floor(timestamp / 1000); + const stringToSign = `${timeSec}\n${secret}`; + const hash = createHmac('sha256', stringToSign).digest(); + + const Signature = hash.toString('base64'); + + return { + timeSec, + Signature, + }; +} + +const chatURL = process.env.LARK_CHAT_URL; +const secret = process.env.LARK_CHAT_SECRET; + +if (!chatURL) { + throw new Error('LARK_CHAT_URL is not set'); +} + +if (!secret) { + throw new Error('LARK_CHAT_SECRET is not set'); +} + +// sendMessage with axios +async function sendMessage({ + downloadURL = '', + actionsJobUrl = '', + gitCommitURL = '', + gitRefURL = '', + triggers = [], +}) { + const { timeSec, Signature } = makeSign(secret); + + // dedupe + triggers = [...new Set(triggers)]; + + const headers = { + 'Content-Type': 'application/json', + 'Signature': Signature, + }; + + const body = { + timestamp: timeSec, + sign: Signature, + // msg_type: 'text', + // content: { + // text: message, + // }, + msg_type: 'post', + content: { + post: { + "zh_cn": { + "title": "πŸš€ ζ–°ηš„ Rabbyx εŒ…ζ‰“ε₯½δΊ† 🌟", + "content": [ + [ + { "tag": "text", "text": `δΈ‹θ½½ι“ΎζŽ₯: ` }, + { "tag": "a", "href": downloadURL, "text": downloadURL } + ], + [ + { "tag": "text", "text": `---------` }, + ], + [ + { "tag": "text", "text": `Actions Job: ` }, + { "tag": "a", "href": actionsJobUrl, "text": actionsJobUrl } + ], + [ + { "tag": "text", "text": `Git Commit: ` }, + { "tag": "a", "href": gitCommitURL, "text": gitCommitURL } + ], + gitRefURL && [ + { "tag": "text", "text": `Git Ref: ` }, + { "tag": "text", "text": gitRefURL } + ], + triggers.length && [ + { "tag": "text", "text": `Triggers: ` }, + { "tag": "text", "text": triggers.join(', ') } + ], + ].filter(Boolean) + } + } + } + }; + + const res = await Axios.post(chatURL, body, { headers }); + console.log(res.data); +} + +const args = process.argv.slice(2); + +if (args[0]) { + sendMessage({ + downloadURL: args[0], + actionsJobUrl: args[1] || process.env.ACTIONS_JOB_URL, + gitCommitURL: args[2] || process.env.GIT_COMMIT_URL, + gitRefURL: process.env.GIT_REF_URL, + triggers: [ + process.env.GITHUB_TRIGGERING_ACTOR, + process.env.GITHUB_ACTOR, + ].filter(Boolean) + }) +} else { + console.log('[notify-lark] no message'); +} \ No newline at end of file diff --git a/scripts/pack.sh b/scripts/pack.sh new file mode 100644 index 000000000..acc5e3218 --- /dev/null +++ b/scripts/pack.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env sh + +script_dir="$( cd "$( dirname "$0" )" && pwd )" +project_dir=$(dirname "$script_dir") + +if [ -d $project_dir/../RabbyDesktop ]; then + RABBY_DESKTOP_REPO=$( cd "$project_dir/../RabbyDesktop" && pwd ) +fi + +export VERSION=$(node --eval="process.stdout.write(require('./package.json').version)"); +export RABBYX_GIT_HASH=$(git rev-parse --short HEAD); +export CURRENT_TIME=$(date +%Y%m%d%H%M%S); + +TARGET_FILE=$project_dir/tmp/RabbyX-v${VERSION}-${RABBYX_GIT_HASH}.zip; + +echo "[pack] VERSION is $VERSION"; +echo "[pack] RABBY_DESKTOP_REPO is $RABBY_DESKTOP_REPO"; + +# for windows, download zip.exe from http://stahlworks.com/dev/index.php?tool=zipunzip and add to your path + +# rm -rf $RABBY_DESKTOP_REPO/assets/chrome_exts/rabby; +if [ -z $NO_BUILD ]; then + yarn; + yarn build:pro; +fi +echo "[pack] built finished"; + +DIST_DIR=$project_dir/dist; +if [ ! -z $RABBY_DESKTOP_REPO ]; then + DIST_DIR=$RABBY_DESKTOP_REPO/assets/chrome_exts/rabby; +fi + +rm -rf $project_dir/tmp/ && mkdir -p $project_dir/tmp/; +if [ -d $DIST_DIR ]; then + cd $DIST_DIR; + zip -r $TARGET_FILE ./* +else + echo "[pack] dist dir not found: $DIST_DIR"; +fi + +cd $project_dir; +cp $TARGET_FILE $project_dir/tmp/RabbyX-latest.zip + +DOWNLOAD_URL="https://download.rabby.io/_tools/RabbyX-v${VERSION}-${RABBYX_GIT_HASH}.zip" + +# upload to storage +if [ -z $NO_UPLOAD ]; then + INVALIDATION_BASE="/_tools/RabbyX-v${VERSION}-${RABBYX_GIT_HASH}*" + JSON="{'Paths': {'Quantity': 2,'Items': ['$INVALIDATION_BASE', '/_tools/RabbyX-latest.zip']}, 'CallerReference': 'cli-rabbyx-${VERSION}-${RABBYX_GIT_HASH}-${CURRENT_TIME}'}" + echo $(node -e "console.log(JSON.stringify($JSON, null, 2))") > "$project_dir/tmp/inv-batch.json" + aws s3 cp $project_dir/tmp/ s3://$RABBY_BUILD_BUCKET/rabby/_tools/ --recursive --exclude="*" --include "*.zip" --acl public-read + echo "[pack] uploaded. DOWNLOAD_URL is $DOWNLOAD_URL"; + + if [ ! -z $CI ]; then + node ./scripts/notify-lark.js "$DOWNLOAD_URL" + else + aws cloudfront create-invalidation --distribution-id E1F7UQCCQWLXXZ --invalidation-batch file://./tmp/inv-batch.json + echo "[pack] invalidation finished."; + fi +fi + +if [ ! -z $RABBY_DESKTOP_REPO ]; then + # cp to RabbyDesktop + rm -rf $RABBY_DESKTOP_REPO/release/rabbyx/ && mkdir -p $RABBY_DESKTOP_REPO/release/rabbyx/; + cp $TARGET_FILE $RABBY_DESKTOP_REPO/release/rabbyx/ + cp $TARGET_FILE $RABBY_DESKTOP_REPO/release/rabbyx/RabbyX-latest.zip +fi + +echo "[pack] finished."; diff --git a/src/background/controller/ens.ts b/src/background/controller/ens.ts new file mode 100644 index 000000000..67de8d2ed --- /dev/null +++ b/src/background/controller/ens.ts @@ -0,0 +1,24 @@ +import { CHAINS } from '@debank/common'; +import { ethers } from 'ethers'; +import { preferenceService } from '../service'; +import buildinProvider from '../utils/buildinProvider'; + +export const getResolver = async (name: string) => { + const account = await preferenceService.getCurrentAccount(); + if (!account) throw new Error('no current account'); + buildinProvider.currentProvider.currentAccount = account.address; + buildinProvider.currentProvider.currentAccountType = account.type; + buildinProvider.currentProvider.currentAccountBrand = account.brandName; + buildinProvider.currentProvider.chainId = CHAINS['ETH'].network; + + const provider = new ethers.providers.Web3Provider( + buildinProvider.currentProvider + ); + return provider.getResolver(name); +}; + +export const getEnsContentHash = async (name: string) => { + return getResolver(name).then((resolver) => { + return resolver.getContentHash(); + }); +}; diff --git a/src/background/controller/mint-rabby.ts b/src/background/controller/mint-rabby.ts new file mode 100644 index 000000000..e3d3a8d62 --- /dev/null +++ b/src/background/controller/mint-rabby.ts @@ -0,0 +1,26 @@ +import { + getMintRabbyContractAddress, + MintRabbyAbi, +} from '@/constant/mint-rabby/mint-rabby-abi'; +import { CHAINS } from '@debank/common'; +import { ethers, Contract } from 'ethers'; +import { preferenceService } from '../service'; +import buildinProvider from '../utils/buildinProvider'; + +export const initMintRabbyContract = async () => { + const account = await preferenceService.getCurrentAccount(); + if (!account) throw new Error('no current account'); + buildinProvider.currentProvider.currentAccount = account.address; + buildinProvider.currentProvider.currentAccountType = account.type; + buildinProvider.currentProvider.currentAccountBrand = account.brandName; + buildinProvider.currentProvider.chainId = CHAINS['ETH'].network; + + const contractAddress = getMintRabbyContractAddress(); + const provider = new ethers.providers.Web3Provider( + buildinProvider.currentProvider + ); + const signer = provider.getSigner(); + const contract = new Contract(contractAddress, MintRabbyAbi, signer); + + return contract; +}; diff --git a/src/background/controller/provider/controller.ts b/src/background/controller/provider/controller.ts index 6abdcb763..68cf0115f 100644 --- a/src/background/controller/provider/controller.ts +++ b/src/background/controller/provider/controller.ts @@ -432,6 +432,15 @@ class ProviderController extends BaseController { }); } + const { r, s, v, ...other } = approvalRes; + sessionService.broadcastToDesktopOnly('transactionChanged', { + type: 'push-tx', + ...other, + value: approvalRes.value || '0x0', + hash: hash, + chain, + }); + stats.report('submitTransaction', { type: currentAccount.brandName, chainId: CHAINS[chain].serverId, @@ -518,6 +527,7 @@ class ProviderController extends BaseController { try { validateGasPriceRange(approvalRes); let hash = ''; + if (RPCService.hasCustomRPC(chain)) { const txData: any = { ...approvalRes, @@ -553,6 +563,7 @@ class ProviderController extends BaseController { traceId ); } + onTransactionSubmitted(hash); return hash; } catch (e: any) { @@ -599,11 +610,15 @@ class ProviderController extends BaseController { ); } const errMsg = e.message || JSON.stringify(e); - notification.create( - undefined, - i18n.t('Transaction push failed'), - errMsg - ); + // notification.create( + // undefined, + // i18n.t('Transaction push failed'), + // errMsg + // ); + sessionService.broadcastToDesktopOnly('transactionChanged', { + type: 'push-failed', + errMsg, + }); transactionHistoryService.removeSigningTx(signingTxId!); throw new Error(errMsg); } @@ -636,7 +651,7 @@ class ProviderController extends BaseController { @Reflect.metadata('SAFE', true) web3ClientVersion = () => { - return `Rabby/${process.env.release}`; + return `RabbyX/${globalThis.rabbyDesktop.appVersion}`; }; @Reflect.metadata('APPROVAL', ['ETHSign', () => null, { height: 390 }]) diff --git a/src/background/controller/provider/rpcFlow.ts b/src/background/controller/provider/rpcFlow.ts index df1547ad1..55e5a2e3c 100644 --- a/src/background/controller/provider/rpcFlow.ts +++ b/src/background/controller/provider/rpcFlow.ts @@ -112,7 +112,7 @@ const flowContext = flow params: { origin, name, icon }, approvalComponent: 'Connect', }, - { height: 800 } + { height: 390 } ); connectOrigins.delete(origin); permissionService.addConnectedSite(origin, name, icon, defaultChain); diff --git a/src/background/controller/safe.ts b/src/background/controller/safe.ts new file mode 100644 index 000000000..0345cc61a --- /dev/null +++ b/src/background/controller/safe.ts @@ -0,0 +1,40 @@ +import { validateEOASign, validateETHSign } from '@/ui/utils/gnosis'; +import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types'; +import { isSameAddress } from '../utils'; + +export const validateConfirmation = ( + txHash: string, + signature: string, + ownerAddress: string, + type: string, + version: string, + safeAddress: string, + tx: SafeTransactionDataPartial, + networkId: number, + owners: string[] +) => { + if (!owners.find((owner) => isSameAddress(owner, ownerAddress))) return false; + switch (type) { + case 'EOA': + try { + return validateEOASign( + signature, + ownerAddress, + tx, + version, + safeAddress, + networkId + ); + } catch (e) { + return false; + } + case 'ETH_SIGN': + try { + return validateETHSign(signature, txHash, ownerAddress); + } catch (e) { + return false; + } + default: + return false; + } +}; diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 265fa350d..6c2a7238d 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -76,6 +76,10 @@ import { addHexPrefix, unpadHexString } from 'ethereumjs-util'; import { ProviderRequest } from './provider/type'; import { QuoteResult } from '@rabby-wallet/rabby-swap/dist/quote'; import transactionWatcher from '../service/transactionWatcher'; +import { getMintRabbyContractAddress } from '@/constant/mint-rabby/mint-rabby-abi'; +import { initMintRabbyContract } from './mint-rabby'; +import { validateConfirmation } from './safe'; +import { getEnsContentHash } from './ens'; const stashKeyrings: Record = {}; @@ -90,23 +94,19 @@ export class WalletController extends BaseController { verifyPassword = (password: string) => keyringService.verifyPassword(password); - setWhitelist = async (password: string, addresses: string[]) => { - await this.verifyPassword(password); + setWhitelist = async (addresses: string[]) => { whitelistService.setWhitelist(addresses); }; - addWhitelist = async (password: string, address: string) => { - await this.verifyPassword(password); + addWhitelist = async (address: string) => { whitelistService.addWhitelist(address); }; - removeWhitelist = async (password: string, address: string) => { - await this.verifyPassword(password); + removeWhitelist = async (address: string) => { whitelistService.removeWhitelist(address); }; - toggleWhitelist = async (password: string, enable: boolean) => { - await this.verifyPassword(password); + toggleWhitelist = async (enable: boolean) => { if (enable) { whitelistService.enableWhitelist(); } else { @@ -357,7 +357,7 @@ export class WalletController extends BaseController { spender: string; pay_token_id: string; unlimited: boolean; - gasPrice: number; + gasPrice?: number; shouldTwoStepApprove: boolean; }, $ctx?: any @@ -367,9 +367,10 @@ export class WalletController extends BaseController { const chainObj = CHAINS[chain]; if (!chainObj) throw new Error(`Can not find chain ${chain}`); try { + let approvalTxHash: string | undefined; if (shouldTwoStepApprove) { unTriggerTxCounter.increase(3); - await this.approveToken( + approvalTxHash = await this.approveToken( chainObj.serverId, pay_token_id, spender, @@ -390,7 +391,7 @@ export class WalletController extends BaseController { if (!shouldTwoStepApprove) { unTriggerTxCounter.increase(2); } - await this.approveToken( + approvalTxHash = await this.approveToken( chainObj.serverId, pay_token_id, spender, @@ -406,7 +407,10 @@ export class WalletController extends BaseController { ); unTriggerTxCounter.decrease(); } - await this.sendRequest({ + if (approvalTxHash) { + return approvalTxHash; + } + const tx: string = await this.sendRequest({ $ctx: needApprove && pay_token_id !== chainObj.nativeTokenAddress ? { @@ -424,12 +428,16 @@ export class WalletController extends BaseController { data: quote.tx.data || '0x', value: `0x${new BigNumber(quote.tx.value || '0').toString(16)}`, chainId: chainObj.id, - gasPrice: `0x${new BigNumber(gasPrice).toString(16)}`, + gasPrice: gasPrice + ? `0x${new BigNumber(gasPrice).toString(16)}` + : undefined, isSwap: true, }, ], }); + unTriggerTxCounter.decrease(); + return tx; } catch (e) { unTriggerTxCounter.reset(); } @@ -541,11 +549,12 @@ export class WalletController extends BaseController { ...extra, }; } - await this.sendRequest({ + const txHash: string = await this.sendRequest({ $ctx, method: 'eth_sendTransaction', params: [tx], }); + return txHash; }; fetchEstimatedL1Fee = async ( @@ -905,6 +914,9 @@ export class WalletController extends BaseController { preferenceService.updateAddressBalance(address, data); return data; }; + updateAddressBalanceCache = (address: string, balance: string) => { + preferenceService.updateAddressUSDValueCache(address, Number(balance)); + }; getAddressCacheBalance = (address: string | undefined) => { if (!address) return null; return preferenceService.getAddressBalance(address); @@ -1488,6 +1500,8 @@ export class WalletController extends BaseController { clearAddressPendingTransactions = (address: string) => { transactionHistoryService.clearPendingTransactions(address); transactionWatcher.clearPendingTx(address); + sessionService.broadcastToDesktopOnly('clearPendingTransactions', null); + return; }; @@ -2067,6 +2081,9 @@ export class WalletController extends BaseController { // getTxExplainCacheByApprovalId = (id: string) => // transactionHistoryService.getExplainCacheByApprovalId(id); + markTransactionAsIndexed = (address: string, chainId: number, hash: string) => + transactionHistoryService.markTransactionAsIndexed(address, chainId, hash); + getTransactionHistory = (address: string) => transactionHistoryService.getList(address); @@ -2447,6 +2464,89 @@ export class WalletController extends BaseController { securityEngineService.disableRule(id); } }; + + mintedRabbyTotal = async () => { + const contract = await initMintRabbyContract(); + const result = await contract.totalSupply(); + + return result.toString(); + }; + + mintedRabbyEndDateTime = async () => { + const contract = await initMintRabbyContract(); + const { publicSaleEnd } = await contract.saleDetails(); + + try { + return new Date(publicSaleEnd.toNumber() * 1000).getTime(); + } catch (e) { + return 0; + } + }; + + getMintedRabby = async () => { + const account = await preferenceService.getCurrentAccount(); + const contract = await initMintRabbyContract(); + const accountAddress = account!.address; + const result = await contract.mintedPerAddress(accountAddress); + const isMinted = (result.totalMints as BigNumber).eq(1); + + if (!isMinted) { + return false; + } + + const nfts = await openapiService.listNFT(accountAddress, true); + const contractAddress = getMintRabbyContractAddress(); + // only one token, so just return the first one + const nft = nfts.find((item) => + isSameAddress(item.contract_id, contractAddress) + ); + + if (!nft) { + return { + contractAddress, + }; + } + + return { + tokenId: nft?.inner_id, + contractAddress: nft?.contract_id, + detailUrl: nft?.detail_url, + }; + }; + + mintRabbyFee = async () => { + const contract = await initMintRabbyContract(); + const feeAmount = (await contract.zoraFeeForAmount(1)).fee; + + return feeAmount.toString(); + }; + + mintRabby = async () => { + const account = await preferenceService.getCurrentAccount(); + const contract = await initMintRabbyContract(); + const feeAmount = await this.mintRabbyFee(); + const value = `0x${new BigNumber(feeAmount).toString(16)}`; + const contractAddress = getMintRabbyContractAddress(); + + const result = await this.sendRequest({ + method: 'eth_sendTransaction', + params: [ + { + chainId: CHAINS['ETH'].id, + value, + from: account!.address, + to: contractAddress, + data: contract.interface.encodeFunctionData('purchase', [1]), + }, + ], + }); + + return result; + }; + + getEnsContentHash = getEnsContentHash; + + validateSafeConfirmation = validateConfirmation; } export default new WalletController(); diff --git a/src/background/desktop-inject/bridge.ts b/src/background/desktop-inject/bridge.ts new file mode 100644 index 000000000..e4163d274 --- /dev/null +++ b/src/background/desktop-inject/bridge.ts @@ -0,0 +1,123 @@ +import { walletController } from "../controller"; +import { openapiService, permissionService, sessionService } from "../service"; + +import { BridgePayload, runAndCatchErr } from "./utils"; + +async function onRabbyxRpcQuery (payload: BridgePayload) { + if (!payload.rpcId) { + throw new Error('[rabbyx-rpc-query] rpcId is required'); + } + + let retPayload = { + result: null, + error: null + } as any; + + switch (payload.method) { + case 'walletController.boot': { + const [password] = payload.params; + retPayload = await runAndCatchErr(() => { + return walletController.boot(password); + }, payload.method) + break; + } + case 'walletController.isBooted': { + retPayload = await runAndCatchErr(() => { + return walletController.isBooted.apply(walletController); + }, payload.method) + break; + } + case 'walletController.isUnlocked': { + retPayload = await runAndCatchErr(() => { + return walletController.isUnlocked.apply(walletController); + }, payload.method) + break; + } + case 'walletController.lockWallet': { + retPayload = await runAndCatchErr(() => { + return walletController.lockWallet.apply(walletController); + }, payload.method) + break; + } + case 'walletController.unlock': { + retPayload = await runAndCatchErr(() => { + return walletController.unlock.apply(walletController, payload.params as any); + }, payload.method) + break; + } + case 'walletController.getConnectedSites': { + retPayload = await runAndCatchErr(() => { + return walletController.getConnectedSites.apply(walletController); + }, payload.method) + break; + } + case 'walletController.importPrivateKey': { + retPayload = await runAndCatchErr(() => { + const [password] = payload.params || []; + return walletController.importPrivateKey.apply(walletController, [password]); + }, payload.method) + break; + } + case 'walletController.getAlianName': { + retPayload = await runAndCatchErr(() => { + const [address] = payload.params || []; + return walletController.getAlianName.apply(walletController, [address]); + }, payload.method) + break; + } + case 'walletController.updateAlianName': { + retPayload = await runAndCatchErr(() => { + const [address, name] = payload.params || []; + return walletController.updateAlianName.apply(walletController, [address, name]); + }, payload.method) + break; + } + default: { + const [ns, method] = payload.method.split('.'); + + if (ns === 'walletController' && typeof walletController[method] === 'function') { + retPayload = await runAndCatchErr(() => { + return walletController[method].apply(walletController, payload.params); + }, payload.method) + } else if (ns === 'permissionService' && typeof permissionService[method] === 'function') { + retPayload = await runAndCatchErr(() => { + return permissionService[method].apply(permissionService, payload.params); + }, payload.method) + } else if (ns === 'sessionService' && typeof sessionService[method] === 'function') { + retPayload = await runAndCatchErr(() => { + return sessionService[method].apply(sessionService, payload.params); + }, payload.method) + } else if (ns === 'openapi' && typeof openapiService[method] === 'function') { + retPayload = await runAndCatchErr(() => { + return openapiService[method].apply(openapiService, payload.params); + }, payload.method) + } else { + retPayload.error = { + message: `[rabbyx-rpc-query] method ${payload.method} is not supported` + } + } + } + } + + // console.debug('[debug] retPayload', retPayload); + + window.rabbyDesktop.ipcRenderer.sendMessage('rabbyx-rpc-respond', JSON.stringify({ + rpcId: payload.rpcId, + result: retPayload?.result, + error: retPayload?.error, + })); +} + +if (window.rabbyDesktop?.ipcRenderer.on) { + console.warn('[debug] window.rabbyDesktop?.ipcRenderer.on', window.rabbyDesktop?.ipcRenderer.on); + window.rabbyDesktop?.ipcRenderer.on('rabbyx-rpc-query', onRabbyxRpcQuery); +} else { + document.addEventListener('rabbyx-rpc-query', (e: any) => { + onRabbyxRpcQuery(e.detail); + }); +} + +(window as any)._walletController = walletController; +(window as any)._permissionService = permissionService; +(window as any)._sessionService = sessionService; +(window as any)._openApi = openapiService; diff --git a/src/background/desktop-inject/type.d.ts b/src/background/desktop-inject/type.d.ts new file mode 100644 index 000000000..a67a7bcca --- /dev/null +++ b/src/background/desktop-inject/type.d.ts @@ -0,0 +1,40 @@ +interface Window { + rabbyDesktop: { + ipcRenderer: { + sendMessage( + channel: T, + ...args: any[] + ): void; + invoke( + channel: U, + ...args: any[] + ): Promise; + on: { + ( + channel: T, + func: (...args: any[]) => void + ): (() => void) | undefined; + ( + channel: T, + func: (event: any) => void + ): (() => void) | undefined; + }; + once: { + ( + channel: T, + func: (...args: any[]) => void + ): (() => void) | undefined; + ( + channel: T, + func: (event: any) => void + ): (() => void) | undefined; + }; + }; + rendererHelpers: { + b64ToObjLink: (b64: string) => string; + bufToObjLink: (buf: Buffer | Uint8Array) => string; + + formatDappURLToShow: (dappURL: string) => string; + }; + }; +} \ No newline at end of file diff --git a/src/background/desktop-inject/utils.ts b/src/background/desktop-inject/utils.ts new file mode 100644 index 000000000..418be3daa --- /dev/null +++ b/src/background/desktop-inject/utils.ts @@ -0,0 +1,36 @@ +export type BridgePayload = { + rpcId: string + method: string + params: any[] +} + +type LooseErrorObj = { + code?: string; + message?: string; + stack?: string; +} +export async function runAndCatchErr(proc: Function, mark?: string): Promise<{ + result: T | null + error?: LooseErrorObj +}> { + try { + console.debug('[debug] runAndCatchErr:: mark', mark); + const result = await proc(); + // console.debug('[debug] runAndCatchErr:: result', result); + + return { + result: result + }; + } catch (err) { + console.error('runAndCatchErr:: err occured', err) + + return { + result: null, + error: { + code: (err as any).code, + message: err.message, + stack: err.stack, + } + }; + } +} \ No newline at end of file diff --git a/src/background/index.ts b/src/background/index.ts index f8e8a9d8b..37f5f96a1 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,7 +1,9 @@ +/// + import { groupBy } from 'lodash'; import 'reflect-metadata'; import * as Sentry from '@sentry/browser'; -import { browser } from 'webextension-polyfill-ts'; +import { browser, Runtime } from 'webextension-polyfill-ts'; import { ethErrors } from 'eth-rpc-errors'; import { WalletController } from 'background/controller/wallet'; import { Message } from '@/utils'; @@ -38,6 +40,8 @@ import { setPopupIcon, wait } from './utils'; import { getSentryEnv } from '@/utils/env'; import { matomoRequestEvent } from '@/utils/matomo-request'; +import './desktop-inject/bridge'; + dayjs.extend(utc); setPopupIcon('default'); @@ -49,7 +53,11 @@ let appStoreLoaded = false; Sentry.init({ dsn: 'https://e871ee64a51b4e8c91ea5fa50b67be6b@o460488.ingest.sentry.io/5831390', - release: process.env.release, + release: globalThis.rabbyDesktop.appVersion, + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, environment: getSentryEnv(), ignoreErrors: [ 'Transport error: {"event":"transport_error","params":["Websocket connection failed"]}', @@ -101,6 +109,8 @@ async function restoreAppState() { transactionWatchService.roll(); initAppMeta(); startEnableUser(); + + window.rabbyDesktop.ipcRenderer.sendMessage('rabbyx-initialized', Date.now()); } restoreAppState(); @@ -171,12 +181,12 @@ restoreAppState(); }); } -// for page provider -browser.runtime.onConnect.addListener((port) => { +const onConnectListner = async (port: Runtime.Port) => { if ( port.name === 'popup' || port.name === 'notification' || - port.name === 'tab' + port.name === 'tab' || + port.name === 'rabbyDesktop' ) { const pm = new PortMessage(port); pm.listen((data) => { @@ -278,6 +288,16 @@ browser.runtime.onConnect.addListener((port) => { port.onDisconnect.addListener((port) => { subscriptionManager.destroy(); }); +} + +// for other extension's such as rabby desktop's shell +browser.runtime.onConnectExternal.addListener(function(port) { + onConnectListner(port); +}); + +// for page provider +browser.runtime.onConnect.addListener((port) => { + onConnectListner(port); }); declare global { diff --git a/src/background/service/keyring/eth-ledger-bridge-keyring.ts b/src/background/service/keyring/eth-ledger-bridge-keyring.ts index 8913eb6a8..3a8bd65e1 100644 --- a/src/background/service/keyring/eth-ledger-bridge-keyring.ts +++ b/src/background/service/keyring/eth-ledger-bridge-keyring.ts @@ -479,16 +479,10 @@ class LedgerBridgeKeyring extends EventEmitter { async _reconnect() { if (this.isWebHID) { await this.cleanUp(); - - let count = 0; // wait connect the WebHID - while (!this.app) { - await this.makeApp(); - await wait(() => { - if (count++ > 50) { - throw new Error('Ledger: Failed to connect to Ledger'); - } - }, 100); + await this.makeApp(); + if (!this.app) { + throw new Error('Ledger: Failed to connect to Ledger'); } } } diff --git a/src/background/service/keyring/index.ts b/src/background/service/keyring/index.ts index 0453066cf..bfec790e4 100644 --- a/src/background/service/keyring/index.ts +++ b/src/background/service/keyring/index.ts @@ -37,6 +37,8 @@ import contactBook from '../contactBook'; import { generateAliasName } from '@/utils/account'; import * as Sentry from '@sentry/browser'; +import './patch'; + export const KEYRING_SDK_TYPES = { SimpleKeyring, HdKeyring, diff --git a/src/background/service/keyring/patch.ts b/src/background/service/keyring/patch.ts new file mode 100644 index 000000000..a9824eda8 --- /dev/null +++ b/src/background/service/keyring/patch.ts @@ -0,0 +1,6 @@ +// patch trezor-like +import TrezorConnect from '@trezor/connect-web'; +import OneKeyConnect from '@onekeyfe/connect'; + +(globalThis as any)._TrezorConnect = TrezorConnect; +(globalThis as any)._OnekeyConnect = OneKeyConnect; diff --git a/src/background/service/notification.ts b/src/background/service/notification.ts index 8bc6b3a2f..7365ac201 100644 --- a/src/background/service/notification.ts +++ b/src/background/service/notification.ts @@ -165,10 +165,6 @@ class NotificationService extends Events { rejectApproval = async (err?: string, stay = false, isInternal = false) => { const approval = this.currentApproval; - if (this.approvals.length <= 1) { - await this.clear(stay); // TODO: FIXME - } - if (isInternal) { approval?.reject && approval?.reject(ethErrors.rpc.internal(err)); } else { @@ -287,13 +283,16 @@ class NotificationService extends Events { return; } } - if (this.notifiWindowId !== null) { browser.windows.update(this.notifiWindowId, { focused: true, }); } else { - this.openNotification(approval.winProps); + this.openNotification( + approval.winProps, + false, + approval.data.approvalComponent + ); } }); }; @@ -331,7 +330,7 @@ class NotificationService extends Events { this.isLocked = true; }; - openNotification = (winProps, ignoreLock = false) => { + openNotification = (winProps, ignoreLock = false, approvalType?: string) => { // Only use ignoreLock flag when approval exist but no notification window exist if (!ignoreLock) { if (this.isLocked) return; @@ -341,6 +340,10 @@ class NotificationService extends Events { winMgr.remove(this.notifiWindowId); this.notifiWindowId = null; } + if (approvalType) { + winProps.query = `type=${approvalType}`; + } + winMgr.openNotification(winProps).then((winId) => { this.notifiWindowId = winId!; }); diff --git a/src/background/service/preference.ts b/src/background/service/preference.ts index 1b43c592a..60d3abf0c 100644 --- a/src/background/service/preference.ts +++ b/src/background/service/preference.ts @@ -12,7 +12,7 @@ import { HARDWARE_KEYRING_TYPES, EVENTS, CHAINS_ENUM } from 'consts'; import { browser } from 'webextension-polyfill-ts'; import semver from 'semver-compare'; -const version = process.env.release || '0'; +const version = globalThis.rabbyDesktop.appVersion || '0'; export interface Account { type: string; @@ -341,6 +341,18 @@ class PreferenceService { getPopupOpen = () => this.popupOpen; + updateAddressUSDValueCache = (address: string, balance: number) => { + const balanceMap = this.store.balanceMap || {}; + const before = this.store.balanceMap[address.toLowerCase()]; + this.store.balanceMap = { + ...balanceMap, + [address.toLowerCase()]: { + total_usd_value: balance, + chain_list: before.chain_list || [], + }, + }; + }; + updateAddressBalance = (address: string, data: TotalBalanceResponse) => { const balanceMap = this.store.balanceMap || {}; this.store.balanceMap = { diff --git a/src/background/service/session.ts b/src/background/service/session.ts index 2c0fcc9a5..a43669c83 100644 --- a/src/background/service/session.ts +++ b/src/background/service/session.ts @@ -61,6 +61,7 @@ const getOrCreateSession = (id: number, origin: string) => { const createSession = (key: string, data?: null | SessionProp) => { const session = new Session(data); sessionMap.set(key, session); + broadcastToDesktopOnly('createSession', key); return session; }; @@ -69,7 +70,19 @@ const deleteSession = (key: string) => { sessionMap.delete(key); }; -const broadcastEvent = (ev, data?, origin?: string) => { +const broadcastToDesktopOnly = (ev: string, data?: any, origin?: string) => { + window.rabbyDesktop?.ipcRenderer.sendMessage( + '__internal_rpc:rabbyx:on-session-broadcast', + { + event: ev, + data, + origin, + } + ); +}; + +const broadcastEvent = (ev: string, data?: any, origin?: string) => { + broadcastToDesktopOnly(ev, data, origin); let sessions: { key: string; data: Session }[] = []; sessionMap.forEach((session, key) => { if (session && permissionService.hasPermission(session.origin)) { @@ -102,4 +115,5 @@ export default { getOrCreateSession, deleteSession, broadcastEvent, + broadcastToDesktopOnly, }; diff --git a/src/background/service/transactionHistory.ts b/src/background/service/transactionHistory.ts index 539e8d8e8..fe030887d 100644 --- a/src/background/service/transactionHistory.ts +++ b/src/background/service/transactionHistory.ts @@ -34,6 +34,8 @@ export interface TransactionGroup { txs: TransactionHistoryItem[]; isPending: boolean; createdAt: number; + completedAt?: number; + dbIndexed: boolean; explain: ObjectType.Merge< ExplainTxResponse, { approvalId: string; calcSuccess: boolean } @@ -43,7 +45,7 @@ export interface TransactionGroup { $ctx?: any; } -interface TxHistoryStore { +export interface TxHistoryStore { transactions: { [key: string]: Record; }; @@ -169,6 +171,7 @@ class TxHistory { explain: explain, isFailed: false, isSubmitFailed: true, + dbIndexed: true, }, }, }; @@ -230,6 +233,7 @@ class TxHistory { isPending: true, explain: explain, isFailed: false, + dbIndexed: false, $ctx, }, }, @@ -409,6 +413,7 @@ class TxHistory { if (!target.isPending) { return; } + target.completedAt = Date.now(); target.isPending = false; target.isFailed = !success; const index = target.txs.findIndex((tx) => tx.hash === hash); @@ -559,6 +564,26 @@ class TxHistory { return maxLocalOrProcessingNonce + 1; } + + markTransactionAsIndexed(address: string, chainId: number, hash: string) { + const list = Object.values( + this.store.transactions[address.toLowerCase()] || {} + ); + const target = list.find((item) => { + return item.chainId === chainId && item.txs.find((i) => i.hash === hash); + }); + if (!target) return; + this.store.transactions = { + ...this.store.transactions, + [address.toLowerCase()]: { + ...this.store.transactions[address.toLowerCase()], + [`${chainId}-${target.nonce}`]: { + ...target, + dbIndexed: true, + }, + }, + }; + } } export default new TxHistory(); diff --git a/src/background/service/transactionWatcher.ts b/src/background/service/transactionWatcher.ts index d53aafe57..1da999cb6 100644 --- a/src/background/service/transactionWatcher.ts +++ b/src/background/service/transactionWatcher.ts @@ -2,6 +2,7 @@ import { openapiService, i18n, transactionHistoryService, + sessionService, } from 'background/service'; import { createPersistStore, isSameAddress } from 'background/utils'; import { notification } from 'background/webapi'; @@ -50,11 +51,17 @@ class TransactionWatcher { }; const url = format(CHAINS[chain].scanLink, hash); - notification.create( + // notification.create( + // url, + // i18n.t('Transaction submitted'), + // i18n.t('click to view more information') + // ); + sessionService.broadcastToDesktopOnly('transactionChanged', { + type: 'submitted', url, - i18n.t('Transaction submitted'), - i18n.t('click to view more information') - ); + hash, + chain, + }); }; checkStatus = async (id: string) => { @@ -94,12 +101,18 @@ class TransactionWatcher { ? i18n.t('Transaction completed') : i18n.t('Transaction failed'); - notification.create( - url, - title, - i18n.t('click to view more information'), - 2 - ); + // notification.create( + // url, + // title, + // i18n.t('click to view more inforwmation'), + // 2 + // ); + sessionService.broadcastToDesktopOnly('transactionChanged', { + type: 'finished', + success: txReceipt.status === '0x1', + hash, + chain, + }); eventBus.emit(EVENTS.broadcastToUI, { method: EVENTS.TX_COMPLETED, diff --git a/src/background/webapi/window.ts b/src/background/webapi/window.ts index e2ea25bff..d3dd58888 100644 --- a/src/background/webapi/window.ts +++ b/src/background/webapi/window.ts @@ -76,10 +76,10 @@ const remove = async (winId) => { return browser.windows.remove(winId); }; -const openNotification = ({ route = '', ...rest } = {}): Promise< +const openNotification = ({ route = '', query = '', ...rest } = {}): Promise< number | undefined > => { - const url = `notification.html${route && `#${route}`}`; + const url = `notification.html${query && `?${query}`}${route && `#${route}`}`; return create({ url, ...rest }); }; diff --git a/src/constant/index.ts b/src/constant/index.ts index cdca4909f..7295c5c17 100644 --- a/src/constant/index.ts +++ b/src/constant/index.ts @@ -43,6 +43,7 @@ import LogoTrust from 'ui/assets/walletlogo/trust.svg'; import IconTrust from 'ui/assets/walletlogo/trust.svg'; import LogoCoolWallet from 'ui/assets/walletlogo/coolwallet.svg'; import IconWatchPurple from 'ui/assets/walletlogo/watch-purple.svg'; +import LogoWatch from 'ui/assets/walletlogo/watchlogo.svg'; import IconWatchWhite from 'ui/assets/walletlogo/IconWatch-white.svg'; import LogoDefiant from 'ui/assets/walletlogo/defiant.svg'; import LogoDefiantWhite from 'ui/assets/walletlogo/defiant.svg'; @@ -53,6 +54,8 @@ import IconWarning from 'ui/assets/sign/security-engine/warning.svg'; import IconError from 'ui/assets/sign/security-engine/error.svg'; import IconProceed from 'ui/assets/sign/security-engine/processed.svg'; import IconClosed from 'ui/assets/sign/security-engine/closed.svg'; +import LogoWalletConnect from 'ui/assets/walletlogo/walletconnect28.svg'; +import IconWalletConnect from 'ui/assets/walletlogo/walletconnect28.svg'; export { CHAINS, CHAINS_ENUM }; @@ -183,14 +186,6 @@ export const GAS_LEVEL_TEXT = { export const IS_WINDOWS = /windows/i.test(global.navigator?.userAgent); -export const LANGS = [ - { - value: 'en', - label: 'English', - icon: IconEN, - }, -]; - export const CHECK_METAMASK_INSTALLED_URL = { Chrome: 'chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/phishing.html', Firefox: '', @@ -325,6 +320,7 @@ export enum WALLET_BRAND_TYPES { COOLWALLET = 'CoolWallet', DEFIANT = 'Defiant', AIRGAP = 'AirGap', + WalletConnect = 'WalletConnect', } enum WALLET_BRAND_CATEGORY { @@ -337,8 +333,8 @@ export type IWalletBrandContent = { id: number; name: string; brand: WALLET_BRAND_TYPES; - icon: string; - image: string; + icon?: string; + image?: string; connectType: BRAND_WALLET_CONNECT_TYPE; category: WALLET_BRAND_CATEGORY; }; @@ -350,8 +346,6 @@ export const WALLET_BRAND_CONTENT: { id: 0, name: 'Amber', brand: WALLET_BRAND_TYPES.AMBER, - icon: IconAmber, - image: LogoAmber, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.INSTITUTIONAL, }, @@ -359,8 +353,6 @@ export const WALLET_BRAND_CONTENT: { id: 10, name: 'BitBox02', brand: WALLET_BRAND_TYPES.BITBOX02, - icon: IconBitBox02, - image: IconBitBox02WithBorder, connectType: BRAND_WALLET_CONNECT_TYPE.BitBox02Connect, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -368,8 +360,6 @@ export const WALLET_BRAND_CONTENT: { id: 1, name: 'Cobo Wallet', brand: WALLET_BRAND_TYPES.COBO, - icon: IconCobo, - image: LogoCobo, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.INSTITUTIONAL, }, @@ -377,8 +367,6 @@ export const WALLET_BRAND_CONTENT: { id: 16, name: 'CoolWallet', brand: WALLET_BRAND_TYPES.COOLWALLET, - icon: LogoCoolWallet, - image: LogoCoolWallet, connectType: BRAND_WALLET_CONNECT_TYPE.QRCodeBase, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -386,8 +374,6 @@ export const WALLET_BRAND_CONTENT: { id: 17, name: 'Defiant', brand: WALLET_BRAND_TYPES.DEFIANT, - icon: LogoDefiant, - image: LogoDefiantWhite, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.MOBILE, }, @@ -395,8 +381,6 @@ export const WALLET_BRAND_CONTENT: { id: 11, name: 'FireBlocks', brand: WALLET_BRAND_TYPES.FIREBLOCKS, - icon: IconFireblocks, - image: IconFireblocksWithBorder, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.INSTITUTIONAL, }, @@ -413,8 +397,6 @@ export const WALLET_BRAND_CONTENT: { id: 12, name: 'GridPlus', brand: WALLET_BRAND_TYPES.GRIDPLUS, - icon: IconGridPlus, - image: IconGridPlus, connectType: BRAND_WALLET_CONNECT_TYPE.GridPlusConnect, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -422,8 +404,6 @@ export const WALLET_BRAND_CONTENT: { id: 2, name: 'imToken', brand: WALLET_BRAND_TYPES.IMTOKEN, - icon: IconImtoken, - image: LogoImtoken, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.MOBILE, }, @@ -431,8 +411,6 @@ export const WALLET_BRAND_CONTENT: { id: 3, name: 'Jade Wallet', brand: WALLET_BRAND_TYPES.JADE, - icon: IconJade, - image: LogoJade, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.INSTITUTIONAL, }, @@ -440,8 +418,6 @@ export const WALLET_BRAND_CONTENT: { id: 15, name: 'Keystone', brand: WALLET_BRAND_TYPES.KEYSTONE, - icon: LogoKeystone, - image: LogoKeystone, connectType: BRAND_WALLET_CONNECT_TYPE.QRCodeBase, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -449,8 +425,6 @@ export const WALLET_BRAND_CONTENT: { id: 18, name: 'AirGap Vault', brand: WALLET_BRAND_TYPES.AIRGAP, - icon: LogoAirGap, - image: LogoAirGap, connectType: BRAND_WALLET_CONNECT_TYPE.QRCodeBase, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -458,8 +432,6 @@ export const WALLET_BRAND_CONTENT: { id: 4, name: 'Ledger', brand: WALLET_BRAND_TYPES.LEDGER, - icon: LogoLedgerWhite, - image: LogoLedgerDark, connectType: BRAND_WALLET_CONNECT_TYPE.LedgerConnect, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -467,8 +439,6 @@ export const WALLET_BRAND_CONTENT: { id: 5, name: 'Math Wallet', brand: WALLET_BRAND_TYPES.MATHWALLET, - icon: IconMath, - image: LogoMath, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.MOBILE, }, @@ -476,8 +446,6 @@ export const WALLET_BRAND_CONTENT: { id: 14, name: 'MetaMask Mobile', brand: WALLET_BRAND_TYPES.METAMASK, - icon: IconMetaMask, - image: IconMetaMask, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.MOBILE, }, @@ -485,8 +453,6 @@ export const WALLET_BRAND_CONTENT: { id: 6, name: 'OneKey', brand: WALLET_BRAND_TYPES.ONEKEY, - icon: IconOnekey, - image: LogoOnekey, connectType: BRAND_WALLET_CONNECT_TYPE.OneKeyConnect, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -494,8 +460,6 @@ export const WALLET_BRAND_CONTENT: { id: 7, name: 'TokenPocket', brand: WALLET_BRAND_TYPES.TP, - icon: IconTokenpocket, - image: LogoTp, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.MOBILE, }, @@ -503,8 +467,6 @@ export const WALLET_BRAND_CONTENT: { id: 8, name: 'Trezor', brand: WALLET_BRAND_TYPES.TREZOR, - icon: IconTrezor, - image: LogoTrezor, connectType: BRAND_WALLET_CONNECT_TYPE.TrezorConnect, category: WALLET_BRAND_CATEGORY.HARDWARE, }, @@ -512,8 +474,15 @@ export const WALLET_BRAND_CONTENT: { id: 9, name: 'Trust Wallet', brand: WALLET_BRAND_TYPES.TRUSTWALLET, - icon: IconTrust, - image: LogoTrust, + connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, + category: WALLET_BRAND_CATEGORY.MOBILE, + }, + [WALLET_BRAND_TYPES.WalletConnect]: { + id: 100, + name: 'Wallet Connect', + brand: WALLET_BRAND_TYPES.WalletConnect, + icon: IconWalletConnect, + image: LogoWalletConnect, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, category: WALLET_BRAND_CATEGORY.MOBILE, }, @@ -528,6 +497,7 @@ export const KEYRING_ICONS = { [HARDWARE_KEYRING_TYPES.Onekey.type]: LogoOnekey, [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24, [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, }; export const KEYRING_ICONS_WHITE = { @@ -556,6 +526,7 @@ export const KEYRINGS_LOGOS = { [HARDWARE_KEYRING_TYPES.Onekey.type]: IconOneKey18, [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24Border, [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, }; export const NOT_CLOSE_UNFOCUS_LIST: string[] = [ diff --git a/src/constant/mint-rabby/gen-tx-detail.ts b/src/constant/mint-rabby/gen-tx-detail.ts new file mode 100644 index 000000000..5b36c4e34 --- /dev/null +++ b/src/constant/mint-rabby/gen-tx-detail.ts @@ -0,0 +1,43 @@ +import { ExplainTxResponse } from '@debank/rabby-api/dist/types'; +import { getMintRabbyContractAddress } from './mint-rabby-abi'; +import IconRabbySVG from 'src/ui/assets/dashboard/rabby.svg'; +import RabbyNFTSVG from './nft.svg'; +import { isSameAddress } from '@/ui/utils'; + +export const genMintRabbyTxDetail = ( + txDetail: ExplainTxResponse +): ExplainTxResponse => { + if (!txDetail.type_call) { + return txDetail; + } + + const { contract } = txDetail.type_call; + if (!isSameAddress(contract, getMintRabbyContractAddress())) { + return txDetail; + } + const nftList = txDetail.balance_change.receive_nft_list; + return { + ...txDetail, + balance_change: { + ...txDetail.balance_change, + receive_nft_list: nftList?.length + ? [ + { + ...nftList[0], + name: 'Rabby Desktop Genesis #' + nftList[0].inner_id, + content: RabbyNFTSVG, + content_type: 'image_url', + collection: { + name: 'Rabby Desktop Genesis', + } as any, + }, + ] + : [], + }, + type_call: { + ...txDetail.type_call, + contract_protocol_name: 'Rabby Desktop', + contract_protocol_logo_url: IconRabbySVG, + }, + }; +}; diff --git a/src/constant/mint-rabby/mint-rabby-abi.tsx b/src/constant/mint-rabby/mint-rabby-abi.tsx new file mode 100644 index 000000000..246c13635 --- /dev/null +++ b/src/constant/mint-rabby/mint-rabby-abi.tsx @@ -0,0 +1,96 @@ +export const TEST_ADDRESS = '0xe473A20617f20f4A7B4fBDD39490380B78430141'; +export const PROD_ADDRESS = '0x1645787ddcb380932130f0d8c22e6bf53a38e725'; + +const { appChannel } = (await window.rabbyDesktop.ipcRenderer.invoke( + 'rabbyx:get-app-version' +)) as { + appChannel: 'reg' | 'prod'; +}; + +export const getMintRabbyContractAddress = () => { + return PROD_ADDRESS; + // if (appChannel === 'prod') { + // return PROD_ADDRESS; + // } + // return TEST_ADDRESS; +}; + +export const MintRabbyAbi = [ + { + inputs: [{ internalType: 'address', name: 'minter', type: 'address' }], + name: 'mintedPerAddress', + outputs: [ + { + components: [ + { internalType: 'uint256', name: 'totalMints', type: 'uint256' }, + { internalType: 'uint256', name: 'presaleMints', type: 'uint256' }, + { internalType: 'uint256', name: 'publicMints', type: 'uint256' }, + ], + internalType: 'struct IERC721Drop.AddressMintDetails', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'quantity', type: 'uint256' }], + name: 'purchase', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + + { + inputs: [{ internalType: 'uint256', name: 'quantity', type: 'uint256' }], + name: 'zoraFeeForAmount', + outputs: [ + { internalType: 'address payable', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'fee', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'saleDetails', + outputs: [ + { + components: [ + { internalType: 'bool', name: 'publicSaleActive', type: 'bool' }, + { internalType: 'bool', name: 'presaleActive', type: 'bool' }, + { internalType: 'uint256', name: 'publicSalePrice', type: 'uint256' }, + { internalType: 'uint64', name: 'publicSaleStart', type: 'uint64' }, + { internalType: 'uint64', name: 'publicSaleEnd', type: 'uint64' }, + { internalType: 'uint64', name: 'presaleStart', type: 'uint64' }, + { internalType: 'uint64', name: 'presaleEnd', type: 'uint64' }, + { + internalType: 'bytes32', + name: 'presaleMerkleRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'maxSalePurchasePerAddress', + type: 'uint256', + }, + { internalType: 'uint256', name: 'totalMinted', type: 'uint256' }, + { internalType: 'uint256', name: 'maxSupply', type: 'uint256' }, + ], + internalType: 'struct IERC721Drop.SaleDetails', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; diff --git a/src/constant/mint-rabby/nft.svg b/src/constant/mint-rabby/nft.svg new file mode 100644 index 000000000..9cf348a95 --- /dev/null +++ b/src/constant/mint-rabby/nft.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/content-script/index.ts b/src/content-script/index.ts index 9fa7187ee..821bcf40f 100644 --- a/src/content-script/index.ts +++ b/src/content-script/index.ts @@ -12,7 +12,11 @@ const ele = document.createElement('script'); // seperate content assignment to two line // use AssetReplacePlugin to replace pageprovider content let content = `var channelName = '${channelName}';`; +content += `if (!window.__RD_isDappSafeView && window.__RD_isDappView) { +;`; content += '#PAGEPROVIDER#'; +content += ` +};`; ele.textContent = content; container.insertBefore(ele, container.children[0]); container.removeChild(ele); diff --git a/src/migrations/migrations.ts b/src/migrations/migrations.ts index 395ab7541..ff2aa61d5 100644 --- a/src/migrations/migrations.ts +++ b/src/migrations/migrations.ts @@ -3,3 +3,4 @@ export { default as daiChainMigration } from './daiChainMigration'; export { default as contactBookMigration } from './contactBookMigration'; export { default as connectedSiteMigration } from './connectedSiteMigration'; export { default as customRPCMigration } from './customRPCMigration'; +export { default as transactionHistoryMigration } from './transactionHistoryMigration'; diff --git a/src/migrations/transactionHistoryMigration.ts b/src/migrations/transactionHistoryMigration.ts new file mode 100644 index 000000000..61ee94a25 --- /dev/null +++ b/src/migrations/transactionHistoryMigration.ts @@ -0,0 +1,25 @@ +import { TxHistoryStore } from 'background/service/transactionHistory'; + +export default { + version: 5, + async migrator(data: { txHistory: TxHistoryStore | undefined }) { + try { + if (!data.txHistory) return undefined; + for (const addr in data.txHistory.transactions) { + const txs = data.txHistory.transactions[addr]; + for (const key in txs) { + txs[key] = { + ...txs[key], + dbIndexed: true, + }; + } + } + return { + txHistory: data.txHistory, + }; + } catch (e) { + // drop custom tokens if migrate failed + return data; + } + }, +}; diff --git a/src/stats.ts b/src/stats.ts index b80546f3d..7168d66f8 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -1,3 +1,3 @@ import StatsReport, { SITE } from '@debank/festats'; -export default new StatsReport(SITE.rabby); +export default new StatsReport(SITE.rabbyDesktop); diff --git a/src/ui/app.tsx b/src/ui/app.tsx index 5769bb700..d2e340416 100644 --- a/src/ui/app.tsx +++ b/src/ui/app.tsx @@ -11,7 +11,7 @@ import { Integrations } from '@sentry/tracing'; import i18n, { addResourceBundle } from 'src/i18n'; import { EVENTS } from 'consts'; -import type { WalletControllerType } from 'ui/utils/WalletContext'; +import { WalletControllerType, WalletProvider } from 'ui/utils/WalletContext'; import store from './store'; @@ -21,7 +21,12 @@ import { getSentryEnv } from '@/utils/env'; Sentry.init({ dsn: 'https://e871ee64a51b4e8c91ea5fa50b67be6b@o460488.ingest.sentry.io/5831390', - release: process.env.release, + release: globalThis.rabbyDesktop.appVersion, + + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, environment: getSentryEnv(), ignoreErrors: [ 'ResizeObserver loop limit exceeded', @@ -110,7 +115,9 @@ store.dispatch.app.initBizStore(); ReactDOM.render( - + + + , document.getElementById('root') -); +); \ No newline at end of file diff --git a/src/ui/assets-const.ts b/src/ui/assets-const.ts new file mode 100644 index 000000000..8db6ed4e1 --- /dev/null +++ b/src/ui/assets-const.ts @@ -0,0 +1,201 @@ +import { + HARDWARE_KEYRING_TYPES, + IWalletBrandContent, + KEYRING_CLASS, + WALLET_BRAND_CONTENT, + WALLET_BRAND_TYPES, +} from '../constant'; + +import IconEN from 'ui/assets/langs/en.svg'; +import IconAmber from 'ui/assets/walletlogo/amber.svg'; +import LogoAmber from 'ui/assets/walletlogo/amber.svg'; +import { + default as IconBitBox02, + default as IconBitBox02WithBorder, +} from 'ui/assets/walletlogo/bitbox.svg'; +import IconCobo from 'ui/assets/walletlogo/cobo.svg'; +import LogoCobo from 'ui/assets/walletlogo/cobo.svg'; +import IconFireblocksWithBorder from 'ui/assets/walletlogo/fireblocks.svg'; +import IconFireblocks from 'ui/assets/walletlogo/fireblocks.svg'; +import IconGnosis from 'ui/assets/walletlogo/gnosis.svg'; +import IconGridPlus from 'ui/assets/walletlogo/gridplus.svg'; +import IconImtoken from 'ui/assets/walletlogo/imtoken.svg'; +import LogoImtoken from 'ui/assets/walletlogo/imtoken.svg'; +import IconJade from 'ui/assets/walletlogo/jade.svg'; +import LogoJade from 'ui/assets/walletlogo/jade.svg'; +import LogoKeystone from 'ui/assets/walletlogo/keystone.svg'; +import LogoAirGap from 'ui/assets/walletlogo/airgap.svg'; +import LogoLedgerDark from 'ui/assets/walletlogo/ledger.svg'; +import LogoLedgerWhite from 'ui/assets/walletlogo/ledger.svg'; +import IconMath from 'ui/assets/walletlogo/math.svg'; +import LogoMath from 'ui/assets/walletlogo/math.svg'; +import IconMetaMask from 'ui/assets/walletlogo/metamask.svg'; +import IconMnemonicInk from 'ui/assets/walletlogo/mnemonic-ink.svg'; +import IconMnemonicWhite from 'ui/assets/walletlogo/IconMnemonic-white.svg'; +import LogoMnemonic from 'ui/assets/walletlogo/mnemoniclogo.svg'; +import IconOnekey from 'ui/assets/walletlogo/onekey.svg'; +import IconOneKey18 from 'ui/assets/walletlogo/onekey.svg'; +import LogoOnekey from 'ui/assets/walletlogo/onekey.svg'; +import IconPrivateKeyWhite from 'ui/assets/walletlogo/private-key-white.svg'; +import IconPrivateKeyInk from 'ui/assets/walletlogo/privatekey-ink.svg'; +import LogoPrivateKey from 'ui/assets/walletlogo/privatekeylogo.svg'; +import LogoTp from 'ui/assets/walletlogo/tp.svg'; +import IconTokenpocket from 'ui/assets/walletlogo/tp.svg'; +import IconTrezor from 'ui/assets/walletlogo/trezor.svg'; +import IconTrezor24Border from 'ui/assets/walletlogo/trezor.svg'; +import IconTrezor24 from 'ui/assets/walletlogo/trezor.svg'; +import LogoTrezor from 'ui/assets/walletlogo/trezor.svg'; +import LogoTrust from 'ui/assets/walletlogo/trust.svg'; +import IconTrust from 'ui/assets/walletlogo/trust.svg'; +import LogoCoolWallet from 'ui/assets/walletlogo/coolwallet.svg'; +import IconWatchPurple from 'ui/assets/walletlogo/watch-purple.svg'; +import IconWatchWhite from 'ui/assets/walletlogo/IconWatch-white.svg'; +import LogoDefiant from 'ui/assets/walletlogo/defiant.svg'; +import LogoDefiantWhite from 'ui/assets/walletlogo/defiant.svg'; +import LogoWalletConnect from 'ui/assets/walletlogo/walletconnect28.svg'; +import IconWalletConnect from 'ui/assets/walletlogo/walletconnect28.svg'; + +const WALLET_BRAND_ASSETS: { + [K in WALLET_BRAND_TYPES]: Required< + Pick + >; +} = { + [WALLET_BRAND_TYPES.AMBER]: { + icon: IconAmber, + image: LogoAmber, + }, + [WALLET_BRAND_TYPES.BITBOX02]: { + icon: IconBitBox02, + image: IconBitBox02WithBorder, + }, + [WALLET_BRAND_TYPES.COBO]: { + icon: IconCobo, + image: LogoCobo, + }, + [WALLET_BRAND_TYPES.COOLWALLET]: { + icon: LogoCoolWallet, + image: LogoCoolWallet, + }, + [WALLET_BRAND_TYPES.DEFIANT]: { + icon: LogoDefiant, + image: LogoDefiantWhite, + }, + [WALLET_BRAND_TYPES.FIREBLOCKS]: { + icon: IconFireblocks, + image: IconFireblocksWithBorder, + }, + [WALLET_BRAND_TYPES.GNOSIS]: { + icon: IconGnosis, + image: IconGnosis, + }, + [WALLET_BRAND_TYPES.GRIDPLUS]: { + icon: IconGridPlus, + image: IconGridPlus, + }, + [WALLET_BRAND_TYPES.IMTOKEN]: { + icon: IconImtoken, + image: LogoImtoken, + }, + [WALLET_BRAND_TYPES.JADE]: { + icon: IconJade, + image: LogoJade, + }, + [WALLET_BRAND_TYPES.KEYSTONE]: { + icon: LogoKeystone, + image: LogoKeystone, + }, + [WALLET_BRAND_TYPES.AIRGAP]: { + icon: LogoAirGap, + image: LogoAirGap, + }, + [WALLET_BRAND_TYPES.LEDGER]: { + icon: LogoLedgerWhite, + image: LogoLedgerDark, + }, + [WALLET_BRAND_TYPES.MATHWALLET]: { + icon: IconMath, + image: LogoMath, + }, + [WALLET_BRAND_TYPES.METAMASK]: { + icon: IconMetaMask, + image: IconMetaMask, + }, + [WALLET_BRAND_TYPES.ONEKEY]: { + icon: IconOnekey, + image: LogoOnekey, + }, + [WALLET_BRAND_TYPES.TP]: { + icon: IconTokenpocket, + image: LogoTp, + }, + [WALLET_BRAND_TYPES.TREZOR]: { + icon: IconTrezor, + image: LogoTrezor, + }, + [WALLET_BRAND_TYPES.TRUSTWALLET]: { + icon: IconTrust, + image: LogoTrust, + }, + [WALLET_BRAND_TYPES.WalletConnect]: { + icon: IconWalletConnect, + image: LogoWalletConnect, + }, +}; + +export const LANGS = [ + { + value: 'en', + label: 'English', + icon: IconEN, + }, +]; + +export function getBrandContent( + k: T +): Required { + return { + ...WALLET_BRAND_ASSETS[k], + ...WALLET_BRAND_CONTENT[k], + }; +} + +export const KEYRING_ICONS = { + [KEYRING_CLASS.MNEMONIC]: IconMnemonicInk, + [KEYRING_CLASS.PRIVATE_KEY]: IconPrivateKeyInk, + [KEYRING_CLASS.WATCH]: IconWatchPurple, + [HARDWARE_KEYRING_TYPES.BitBox02.type]: IconBitBox02, + [HARDWARE_KEYRING_TYPES.Ledger.type]: LogoLedgerWhite, + [HARDWARE_KEYRING_TYPES.Onekey.type]: LogoOnekey, + [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24, + [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, +}; + +export const KEYRING_ICONS_WHITE = { + [KEYRING_CLASS.MNEMONIC]: IconMnemonicWhite, + [KEYRING_CLASS.PRIVATE_KEY]: IconPrivateKeyWhite, + [KEYRING_CLASS.WATCH]: IconWatchWhite, + [HARDWARE_KEYRING_TYPES.BitBox02.type]: IconBitBox02, + [HARDWARE_KEYRING_TYPES.Ledger.type]: LogoLedgerWhite, + [HARDWARE_KEYRING_TYPES.Onekey.type]: LogoOnekey, + [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24, + [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, +}; +export const KEYRING_PURPLE_LOGOS = { + [KEYRING_CLASS.MNEMONIC]: IconMnemonicInk, + [KEYRING_CLASS.PRIVATE_KEY]: IconPrivateKeyInk, + [KEYRING_CLASS.WATCH]: IconWatchPurple, +}; + +export const KEYRINGS_LOGOS = { + [KEYRING_CLASS.MNEMONIC]: LogoMnemonic, + [KEYRING_CLASS.PRIVATE_KEY]: LogoPrivateKey, + [KEYRING_CLASS.WATCH]: IconWatchWhite, + [HARDWARE_KEYRING_TYPES.BitBox02.type]: IconBitBox02WithBorder, + [HARDWARE_KEYRING_TYPES.Ledger.type]: LogoLedgerWhite, + [HARDWARE_KEYRING_TYPES.Onekey.type]: IconOneKey18, + [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24Border, + [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, +}; diff --git a/src/ui/assets/walletlogo/gnosis.svg b/src/ui/assets/walletlogo/gnosis.svg index 7f5ac7506..e10a65199 100644 --- a/src/ui/assets/walletlogo/gnosis.svg +++ b/src/ui/assets/walletlogo/gnosis.svg @@ -1,9 +1,18 @@ - - + + + + + + + - + - + + + + + diff --git a/src/ui/assets/walletlogo/walletconnect28.svg b/src/ui/assets/walletlogo/walletconnect28.svg new file mode 100644 index 000000000..ef4b309ab --- /dev/null +++ b/src/ui/assets/walletlogo/walletconnect28.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/ui/component/AccountSelectDrawer/index.tsx b/src/ui/component/AccountSelectDrawer/index.tsx index fede940ca..28857648a 100644 --- a/src/ui/component/AccountSelectDrawer/index.tsx +++ b/src/ui/component/AccountSelectDrawer/index.tsx @@ -8,10 +8,12 @@ import { Account } from 'background/service/preference'; import { useWallet, isSameAddress, formatTokenAmount } from 'ui/utils'; import { KEYRING_TYPE, - KEYRING_ICONS, WALLET_BRAND_CONTENT, CHAINS, } from 'consts'; +import { + KEYRING_ICONS, +} from 'ui/assets-const'; import './style.less'; interface AccountSelectDrawerProps { diff --git a/src/ui/component/AddressList/AddressItem.tsx b/src/ui/component/AddressList/AddressItem.tsx index eaa169bd4..86aa332b8 100644 --- a/src/ui/component/AddressList/AddressItem.tsx +++ b/src/ui/component/AddressList/AddressItem.tsx @@ -13,11 +13,13 @@ import { Account } from 'background/service/preference'; import { useWallet } from 'ui/utils'; import { AddressViewer, Copy } from 'ui/component'; import { - KEYRING_ICONS, WALLET_BRAND_CONTENT, KEYRING_TYPE_TEXT, BRAND_ALIAN_TYPE_TEXT, } from 'consts'; +import { + KEYRING_ICONS, +} from 'ui/assets-const'; import IconEditPen from 'ui/assets/editpen.svg'; import IconCorrect from 'ui/assets/dashboard/contacts/correct.png'; diff --git a/src/ui/component/Contact/ListModal.tsx b/src/ui/component/Contact/ListModal.tsx index 9587dfbf0..61f5c4a79 100644 --- a/src/ui/component/Contact/ListModal.tsx +++ b/src/ui/component/Contact/ListModal.tsx @@ -90,7 +90,7 @@ const ListModal = ({ visible, onOk, onCancel }: ListModalProps) => { cancelText: 'Cancel', title: 'Save to Whitelist', validationHandler: async (password: string) => - wallet.setWhitelist(password, list), + wallet.setWhitelist(list), onFinished() { setEditWhitelistVisible(false); dispatch.whitelist.getWhitelist(); diff --git a/src/ui/hooks/useClasses.ts b/src/ui/hooks/useClasses.ts new file mode 100644 index 000000000..70b55ac2a --- /dev/null +++ b/src/ui/hooks/useClasses.ts @@ -0,0 +1,25 @@ +import { useMemo, useLayoutEffect } from "react"; + +export function arraify(value: T | T[]): T[] { + return Array.isArray(value) ? value : [value]; +} + +export function useBodyClassNameOnMounted( + className: (string | string | number | boolean)[] +) { + const classNames = useMemo(() => { + return arraify(className).filter(Boolean).join(' '); + }, [className]); + + useLayoutEffect(() => { + classNames.split(' ').forEach((name) => { + document.body.classList.add(name); + }); + + return () => { + classNames.split(' ').forEach((name) => { + document.body.classList.remove(name); + }); + }; + }, [classNames]); +} \ No newline at end of file diff --git a/src/ui/popup.ejs b/src/ui/popup.ejs new file mode 100644 index 000000000..13ead7fd9 --- /dev/null +++ b/src/ui/popup.ejs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/src/ui/popup.html b/src/ui/popup.html deleted file mode 100644 index 6d5e556ed..000000000 --- a/src/ui/popup.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - -
- - - diff --git a/src/ui/style/antd-overwrite.less b/src/ui/style/antd-overwrite.less index d55e618df..01614cd7d 100644 --- a/src/ui/style/antd-overwrite.less +++ b/src/ui/style/antd-overwrite.less @@ -41,19 +41,29 @@ right: 0; bottom: 0; left: 0; - background-color: rgba(45, 48, 51, 0.1); opacity: 0; transition: opacity 0.2s ease-in-out; } + + &:not(.ant-btn-background-ghost)::before { + background-color: rgba(45, 48, 51, 0.1); + } } -.ant-btn-primary { +.ant-btn-primary:not(.ant-btn-background-ghost) { &:hover, &:focus { background-color: @primary-color; border-color: @primary-color; box-shadow: 0px 8px 16px rgba(134, 151, 255, 0.3); } + &[disabled] { + color: white; + background: #e5e9ef; + border-color: #e5e9ef; + text-shadow: none; + box-shadow: none; + } } /*--------------------- message diff --git a/src/ui/utils/index.ts b/src/ui/utils/index.ts index e278020d8..30581eeb8 100644 --- a/src/ui/utils/index.ts +++ b/src/ui/utils/index.ts @@ -3,9 +3,11 @@ import { CHECK_METAMASK_INSTALLED_URL, WALLET_BRAND_CONTENT, KEYRING_CLASS, +} from 'consts'; +import { KEYRINGS_LOGOS, KEYRING_PURPLE_LOGOS, -} from 'consts'; +} from 'ui/assets-const'; import { Account } from 'background/service/preference'; // eslint-disable-next-line @typescript-eslint/no-empty-function export const noop = () => {}; diff --git a/src/ui/utils/url.ts b/src/ui/utils/url.ts index 46ece1e12..3cad71021 100644 --- a/src/ui/utils/url.ts +++ b/src/ui/utils/url.ts @@ -18,3 +18,7 @@ export const obj2query = (obj: Record) => { export const isValidateUrl = (url: string) => { return /^((https|http)?:\/\/)[^\s]+\.[^\s]+/.test(url); }; + +export const formatDappURLToShow = (url: string) => { + return window.rabbyDesktop?.rendererHelpers?.formatDappURLToShow?.(url) || url; +} \ No newline at end of file diff --git a/src/ui/views/AddressDetail/AddressInfo.tsx b/src/ui/views/AddressDetail/AddressInfo.tsx index 114049a3a..edeca438d 100644 --- a/src/ui/views/AddressDetail/AddressInfo.tsx +++ b/src/ui/views/AddressDetail/AddressInfo.tsx @@ -16,7 +16,8 @@ import IconPen from 'ui/assets/editpen.svg'; import './style.less'; import { copyAddress } from '@/ui/utils/clipboard'; import { useForm } from 'antd/lib/form/Form'; -import { KEYRING_CLASS, KEYRING_ICONS, WALLET_BRAND_CONTENT } from '@/constant'; +import { KEYRING_CLASS, WALLET_BRAND_CONTENT } from '@/constant'; +import { KEYRING_ICONS } from 'ui/assets-const'; import { useLocation } from 'react-router-dom'; import { connectStore, useRabbyGetter, useRabbySelector } from '@/ui/store'; import IconTagYou from 'ui/assets/tag-you.svg'; diff --git a/src/ui/views/AddressDetail/index.tsx b/src/ui/views/AddressDetail/index.tsx index 591886687..ac1c00c45 100644 --- a/src/ui/views/AddressDetail/index.tsx +++ b/src/ui/views/AddressDetail/index.tsx @@ -45,9 +45,9 @@ const AddressDetail = () => { wallet, validationHandler: async (password) => { if (checked) { - await wallet.addWhitelist(password, address); + await wallet.addWhitelist(address); } else { - await wallet.removeWhitelist(password, address); + await wallet.removeWhitelist(address); } }, onFinished() { diff --git a/src/ui/views/AddressManagement/AddressItem.tsx b/src/ui/views/AddressManagement/AddressItem.tsx index 546f4fe84..194faec61 100644 --- a/src/ui/views/AddressManagement/AddressItem.tsx +++ b/src/ui/views/AddressManagement/AddressItem.tsx @@ -2,12 +2,14 @@ import { message, Tooltip } from 'antd'; import clsx from 'clsx'; import { BRAND_ALIAN_TYPE_TEXT, - KEYRINGS_LOGOS, KEYRING_CLASS, - KEYRING_ICONS, KEYRING_TYPE_TEXT, WALLET_BRAND_CONTENT, } from 'consts'; +import { + KEYRINGS_LOGOS, + KEYRING_ICONS, +} from 'ui/assets-const'; import React, { memo, MouseEventHandler, diff --git a/src/ui/views/Approval/components/AccountCard.tsx b/src/ui/views/Approval/components/AccountCard.tsx index 39b9a5db6..8001e63cb 100644 --- a/src/ui/views/Approval/components/AccountCard.tsx +++ b/src/ui/views/Approval/components/AccountCard.tsx @@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react'; import { Account } from 'background/service/preference'; import { useWallet } from 'ui/utils'; import { splitNumberByStep } from 'ui/utils/number'; -import { KEYRINGS_LOGOS, WALLET_BRAND_CONTENT, KEYRING_CLASS } from 'consts'; +import { WALLET_BRAND_CONTENT, KEYRING_CLASS } from 'consts'; +import { KEYRINGS_LOGOS } from 'ui/assets-const'; import { AddressViewer } from 'ui/component'; import useCurrentBalance from 'ui/hooks/useCurrentBalance'; import clsx from 'clsx'; diff --git a/src/ui/views/Approval/components/Connect/index.tsx b/src/ui/views/Approval/components/Connect/index.tsx index 612a11999..8569929f8 100644 --- a/src/ui/views/Approval/components/Connect/index.tsx +++ b/src/ui/views/Approval/components/Connect/index.tsx @@ -1,23 +1,13 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Button } from 'antd'; -import clsx from 'clsx'; import { useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Chain } from 'background/service/openapi'; import { ChainSelector, Spin, FallbackSiteLogo } from 'ui/component'; import { useApproval, useWallet } from 'ui/utils'; import { CHAINS_ENUM, CHAINS } from 'consts'; -import styled from 'styled-components'; -import UserDataList from './UserDataList'; -import { - ContextActionData, - RuleConfig, - Level, -} from '@debank/rabby-security-engine/dist/rules'; -import { Result } from '@debank/rabby-security-engine'; -import { useSecurityEngine } from 'ui/utils/securityEngine'; -import RuleResult from './RuleResult'; -import RuleDrawer from '../SecurityEngine/RuleDrawer'; +import { useBodyClassNameOnMounted } from '@/ui/hooks/useClasses'; +import { formatDappURLToShow } from '@/ui/utils/url'; interface ConnectProps { params: any; @@ -25,117 +15,6 @@ interface ConnectProps { defaultChain?: CHAINS_ENUM; } -const ConnectWrapper = styled.div` - height: 100vh; - display: flex; - flex-direction: column; - .approval-connect { - padding: 26px 20px; - .approval-title { - font-weight: 500; - font-size: 17px; - line-height: 20px; - color: #13141a; - } - .chain-selector { - height: 32px; - border-radius: 8px; - background: #fff; - font-size: 13px; - border: 1px solid #e5e9ef; - box-shadow: none; - .chain-icon-comp { - width: 16px; - height: 16px; - img { - width: 16px; - height: 16px; - } - } - &.hover { - background: rgba(134, 151, 255, 0.1); - } - } - .connect-card { - background: #f5f6fa; - border-radius: 8px; - padding: 20px; - display: flex; - flex-direction: column; - align-items: center; - position: relative; - .connect-origin { - margin-top: 12px; - margin-bottom: 14px; - font-weight: 500; - font-size: 22px; - line-height: 26px; - text-align: center; - color: #13141a; - } - } - } - .rule-list { - flex: 1; - overflow: auto; - padding: 0 20px; - } -`; - -const Footer = styled.div` - display: flex; - flex-direction: column; - padding: 20px; - border-top: 1px solid #e5e9ef; - width: 100%; - background-color: #fff; - .ant-btn { - width: 100%; - height: 52px; - &:nth-child(1) { - margin-bottom: 12px; - } - &:nth-last-child(1) { - margin-top: 20px; - } - } - .security-tip { - font-weight: 500; - font-size: 13px; - line-height: 15px; - text-align: center; - color: #4b4d59; - } -`; - -const RuleDesc = [ - { - id: '1004', - desc: 'Listed by community platform', - fixed: true, - }, - { - id: '1005', - desc: 'Site popularity', - fixed: true, - }, - { - id: '1001', - desc: 'Phishing check by Rabby', - fixed: false, - }, - { - id: '1002', - desc: 'Phishing check by MetaMask', - fixed: false, - }, - { - id: '1003', - desc: 'Phishing check by ScamSniffer', - fixed: false, - }, -]; - const Connect = ({ params: { icon, origin } }: ConnectProps) => { const { state } = useLocation<{ showChainsModal?: boolean; @@ -147,188 +26,12 @@ const Connect = ({ params: { icon, origin } }: ConnectProps) => { const wallet = useWallet(); const [defaultChain, setDefaultChain] = useState(CHAINS_ENUM.ETH); const [isLoading, setIsLoading] = useState(true); - const [processedRules, setProcessedRules] = useState([]); - const [nonce, setNonce] = useState(0); - const { rules, userData, executeEngine } = useSecurityEngine(nonce); - const [engineResults, setEngineResults] = useState([]); - const [collectList, setCollectList] = useState< - { name: string; logo_url: string }[] - >([]); - const [originPopularLevel, setOriginPopularLevel] = useState( - null - ); - const [ruleDrawerVisible, setRuleDrawerVisible] = useState(false); - const [selectRule, setSelectRule] = useState<{ - ruleConfig: RuleConfig; - value?: number | string | boolean; - level?: Level; - ignored: boolean; - } | null>(null); - - const userListResult = useMemo(() => { - const originBlacklist = engineResults.find( - (result) => result.id === '1006' - ); - const originWhitelist = engineResults.find( - (result) => result.id === '1007' - ); - return originBlacklist || originWhitelist; - }, [engineResults]); - - const sortRules = useMemo(() => { - const list: { id: string; desc: string; result: Result | null }[] = []; - for (let i = 0; i < RuleDesc.length; i++) { - const item = RuleDesc[i]; - const result = engineResults.find((result) => result.id === item.id); - if (result || item.fixed) { - list.push({ - id: item.id, - desc: item.desc, - result: result || null, - }); - } - } - return list; - }, [engineResults]); - - const connectBtnStatus = useMemo(() => { - let disabled = false; - let text = ''; - let forbiddenCount = 0; - let safeCount = 0; - let needProcessCount = 0; - let cancelBtnText = 'Cancel'; - - engineResults.forEach((result) => { - if (result.level === Level.SAFE) { - safeCount++; - } else if (result.level === Level.FORBIDDEN) { - forbiddenCount++; - } else if ( - result.level !== Level.ERROR && - result.level !== Level.CLOSED && - !processedRules.includes(result.id) - ) { - needProcessCount++; - } - }); - - if (forbiddenCount > 0) { - disabled = true; - text = `Found ${forbiddenCount} forbidden risk${ - forbiddenCount > 1 ? 's' : '' - }. Connection is blocked.`; - cancelBtnText = 'Close'; - } else if (needProcessCount > 0) { - if (safeCount > 0) { - disabled = false; - text = ''; - } else { - disabled = true; - text = `Found ${needProcessCount} risk${ - needProcessCount > 1 ? 's' : '' - }. Please process ${ - needProcessCount > 1 ? 'them' : 'it' - } before connecting.`; - } - } - - return { - disabled, - text, - cancelBtnText, - }; - }, [engineResults, processedRules]); - - const hasForbidden = useMemo(() => { - return engineResults.some((item) => item.level === Level.FORBIDDEN); - }, [engineResults]); - - const hasSafe = useMemo(() => { - return engineResults.some((item) => item.level === Level.SAFE); - }, [engineResults]); - - const handleIgnoreRule = (id: string) => { - setProcessedRules([...processedRules, id]); - if (selectRule) { - setSelectRule({ - ...selectRule, - ignored: true, - }); - } - setRuleDrawerVisible(false); - }; - - const handleUndoIgnore = (id: string) => { - setProcessedRules(processedRules.filter((item) => item !== id)); - if (selectRule) { - setSelectRule({ - ...selectRule, - ignored: false, - }); - } - setRuleDrawerVisible(false); - }; - - const handleRuleEnableStatusChange = async (id: string, value: boolean) => { - if (processedRules.includes(id)) { - setProcessedRules(processedRules.filter((i) => i !== id)); - } - await wallet.ruleEnableStatusChange(id, value); - setNonce(nonce + 1); - }; - const handleUserDataListChange = () => { - setNonce(nonce + 1); - }; - - const handleExecuteSecurityEngine = async () => { - setIsLoading(true); - const ctx: ContextActionData = { - origin: { - url: origin, - communityCount: collectList.length, - popularLevel: originPopularLevel!, - }, - }; - const results = await executeEngine(ctx); - setIsLoading(false); - setEngineResults(results); - }; + useBodyClassNameOnMounted(['__rabbyx-approval-connect']); const init = async () => { const account = await wallet.getCurrentAccount(); const site = await wallet.getSite(origin); - let level: 'very_low' | 'low' | 'medium' | 'high'; - let collectList: { name: string; logo_url: string }[] = []; - try { - const result = await wallet.openapi.getOriginPopularityLevel(origin); - setOriginPopularLevel(result.level); - level = result.level; - } catch (e) { - setOriginPopularLevel('low'); - level = 'low'; - } - try { - const { - collect_list, - } = await wallet.openapi.getOriginThirdPartyCollectList(origin); - setCollectList(collect_list); - collectList = collect_list; - } catch (e) { - setCollectList([]); - } - - const ctx: ContextActionData = { - origin: { - url: origin, - communityCount: collectList.length, - popularLevel: level, - }, - }; - const results = await executeEngine(ctx); - - setEngineResults(results); if (site) { setDefaultChain(site.chain); setIsLoading(false); @@ -362,133 +65,93 @@ const Connect = ({ params: { icon, origin } }: ConnectProps) => { rejectApproval('User rejected the request.'); }; + const addOriginFeedback = React.useCallback(async (isSafe: boolean) => { + const account = await wallet.getCurrentAccount(); + + wallet.openapi.addOriginFeedback({ + origin, + is_safe: isSafe, + user_addr: account!.address, + }); + }, []); + + const handleDetectContinue = React.useCallback(() => { + addOriginFeedback(true); + }, []); + + const handleDetectCancel = React.useCallback(() => { + handleCancel(); + addOriginFeedback(false); + }, []); + const handleAllow = async () => { resolveApproval({ defaultChain, }); }; - const handleRuleDrawerClose = (update: boolean) => { - if (update) { - handleExecuteSecurityEngine(); - } - setRuleDrawerVisible(false); - }; - const handleChainChange = (val: CHAINS_ENUM) => { setDefaultChain(val); }; - const handleSelectRule = (rule: { - id: string; - desc: string; - result: Result | null; - }) => { - const target = rules.find((item) => item.id === rule.id); - if (!target) return; - setSelectRule({ - ruleConfig: target, - value: rule.result?.value, - level: rule.result?.level, - ignored: processedRules.includes(rule.id), - }); - setRuleDrawerVisible(true); - }; - return ( - -
-
-
Connect to Dapp
+
+
+ {t('Website Wants to Connect')} +
+
+
+
+ +
+
+

{formatDappURLToShow(origin)}

+
+
+
+

+ {t('On this site use chain')} +

Select a chain to connect for
-
{origin}
+
{formatDappURLToShow(origin)}
} value={defaultChain} onChange={handleChainChange} connection showModal={showModal} - modalHeight={540} - /> -
-
- -

{origin}

-
- -
- {sortRules.map((rule) => ( - - ))} +
+ +
+
+ +
- -
-
- - {connectBtnStatus.text && ( -
{connectBtnStatus.text}
- )} - -
-
- - +
); }; diff --git a/src/ui/views/Approval/components/Decrypt.tsx b/src/ui/views/Approval/components/Decrypt.tsx index 73559863d..151264858 100644 --- a/src/ui/views/Approval/components/Decrypt.tsx +++ b/src/ui/views/Approval/components/Decrypt.tsx @@ -121,6 +121,7 @@ const GetEncryptionPublicKey = ({ params }: ConnectProps) => { {canProcess ? (
This website would like your public encryption key. By consenting, diff --git a/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx b/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx index 7b8a52790..b05ca3a72 100644 --- a/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx +++ b/src/ui/views/Approval/components/LedgerHardwareWaiting.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useState } from 'react'; import { Button, message } from 'antd'; import { useTranslation } from 'react-i18next'; import { matomoRequestEvent } from '@/utils/matomo-request'; @@ -181,6 +181,14 @@ const LedgerHardwareWaiting = ({ params }: { params: ApprovalParams }) => { useEffect(() => { init(); }, []); + + useLayoutEffect(() => { + document.body.classList.add('page-LedgerHardwareWaiting'); + + return () => { + document.body.classList.remove('page-LedgerHardwareWaiting'); + } + }, []); const currentHeader = statusHeaders[connectStatus]; if (connectStatus === WALLETCONNECT_STATUS_MAP.FAILD) { diff --git a/src/ui/views/Approval/components/SignText.tsx b/src/ui/views/Approval/components/SignText.tsx index 8c4e8af59..5ab7e0635 100644 --- a/src/ui/views/Approval/components/SignText.tsx +++ b/src/ui/views/Approval/components/SignText.tsx @@ -284,7 +284,8 @@ const SignText = ({ params }: { params: SignTextProps }) => {
- Unable to sign because the current address is a Watch-only Address + You can't sign with a watch-only address from contacts. To sign, you'll need to use a different address. + {/* Unable to sign because the current address is a Watch-only Address from Contacts. You can{' '} { > import it {' '} - fully or use another address. + fully or use another address. */}
); @@ -427,11 +428,13 @@ const SignText = ({ params }: { params: SignTextProps }) => { className="w-[172px]" onClick={() => handleAllow(forceProcess)} loading={isLoading} - disabled={ - (isLedger && !useLedgerLive && !hasConnectedLedgerHID) || + {...(isLedger && !useLedgerLive && !hasConnectedLedgerHID) || !forceProcess || - securityCheckStatus === 'loading' - } + securityCheckStatus === 'loading' ? { + disabled: true + } : { + ghost: true + }} > {t(submitText)} diff --git a/src/ui/views/Approval/components/SignTx.tsx b/src/ui/views/Approval/components/SignTx.tsx index b3f2b43f7..11d4e59f1 100644 --- a/src/ui/views/Approval/components/SignTx.tsx +++ b/src/ui/views/Approval/components/SignTx.tsx @@ -66,6 +66,7 @@ import { useLedgerDeviceConnected } from '@/utils/ledger'; import { TransactionGroup } from 'background/service/transactionHistory'; import { intToHex } from 'ui/utils/number'; import { calcMaxPriorityFee } from '@/utils/transaction'; +import { genMintRabbyTxDetail } from '@/constant/mint-rabby/gen-tx-detail'; const normalizeHex = (value: string | number) => { if (typeof value === 'number') { @@ -111,7 +112,7 @@ const normalizeTxParams = (tx) => { }; export const TxTypeComponent = ({ - txDetail, + txDetail: oldTxDetail, chain = CHAINS[CHAINS_ENUM.ETH], isReady, raw, @@ -129,6 +130,10 @@ export const TxTypeComponent = ({ }) => { if (!isReady) return ; + const txDetail = useMemo(() => genMintRabbyTxDetail(oldTxDetail), [ + oldTxDetail, + ]); + if (txDetail.type_deploy_contract) return ( {
You can't sign with a watch-only address from contacts. To sign, - you'll need to{' '} + you'll need to use a different address. + {/* Unable to sign because the current address is a Watch-only Address + from Contacts. You can{' '} { > import it {' '} - fully or use a different address. + fully or use another address. */}
); @@ -1757,6 +1764,7 @@ const SignTx = ({ params, origin }: SignTxProps) => {