diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1ff93fc11..c3f143bb5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,107 +1,72 @@
-# 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: 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: '18.16.1'
- - name: Get yarn cache directory path
- id: yarn-cache-dir-path
- run: echo "::set-output name=dir::$(yarn cache dir)"
+ # - 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-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') }}
+ # - 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: 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
- 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: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
+ runs-on: [self-hosted, X64, Linux, builder]
needs: setup
env:
NODE_OPTIONS: '--max_old_space_size=4096'
@@ -123,23 +88,79 @@ jobs:
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') }}
+ key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('patches/*.patch') }}
- 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 cf63bf973..a046ec912 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ dist-*
.DS_Store
yarn-error
-tmp/*.zip
\ No newline at end of file
+tmp/*.zip
+tmp
diff --git a/_raw/blank.html b/_raw/blank.html
new file mode 100644
index 000000000..162208cae
--- /dev/null
+++ b/_raw/blank.html
@@ -0,0 +1,7 @@
+
+
+ RabbyX Blank Page
+
+
+
+
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 1f0abe33f..46d79542e 100644
--- a/build/webpack.common.config.js
+++ b/build/webpack.common.config.js
@@ -40,6 +40,11 @@ const config = {
sideEffects: true,
test: /[\\/]pageProvider[\\/]index.ts/,
loader: 'ts-loader',
+ options: {
+ compilerOptions: {
+ outDir: paths.dist,
+ },
+ }
},
{
test: /[\\/]ui[\\/]index.tsx/,
@@ -59,6 +64,7 @@ const config = {
}),
compilerOptions: {
module: 'es2015',
+ outDir: paths.dist,
},
},
},
@@ -97,6 +103,9 @@ const config = {
}),
],
}),
+ compilerOptions: {
+ outDir: paths.dist,
+ }
},
},
],
@@ -179,9 +188,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 1d4aad4e2..66f4d0a35 100644
--- a/package.json
+++ b/package.json
@@ -3,8 +3,8 @@
"version": "0.92.46",
"description": "A browser plugin for DeFi users",
"scripts": {
- "clean": "mkdir -p dist && rm -rf dist/*",
- "make-theme": "node ./scripts/make-theme.js",
+ "clean": "sh ./scripts/clean-assets.sh",
+ "make-theme": "node scripts/make-theme.js",
"build:dev": "npm run clean && npm run make-theme && TAILWIND_MODE=watch webpack --progress --env config=dev",
"build:pro": "npm run clean && npm run make-theme && webpack --progress --env config=pro",
"build:debug": "npm run clean && npm run make-theme && webpack --progress --env config=debug",
@@ -17,7 +17,7 @@
"dependencies": {
"@coinbase/wallet-sdk": "3.8.0-beta.3",
"@debank/common": "0.3.51",
- "@debank/festats": "1.0.0",
+ "@debank/festats": "1.0.3",
"@dnd-kit/core": "5.0.1",
"@dnd-kit/sortable": "6.0.0",
"@dnd-kit/utilities": "3.0.2",
@@ -91,6 +91,7 @@
"human-standard-token-abi": "2.0.0",
"immer": "9.0.21",
"interval-promise": "1.4.0",
+ "jotai": "^2.1.0",
"koa-compose": "4.1.0",
"lodash": "4.17.21",
"loglevel": "1.7.1",
diff --git a/patches/@rabby-wallet+eth-lattice-keyring+1.0.5.patch b/patches/@rabby-wallet+eth-lattice-keyring+1.0.5.patch
new file mode 100644
index 000000000..609d397fb
--- /dev/null
+++ b/patches/@rabby-wallet+eth-lattice-keyring+1.0.5.patch
@@ -0,0 +1,86 @@
+diff --git a/node_modules/@rabby-wallet/eth-lattice-keyring/index.js b/node_modules/@rabby-wallet/eth-lattice-keyring/index.js
+index 9e934aa..1fc8d5c 100644
+--- a/node_modules/@rabby-wallet/eth-lattice-keyring/index.js
++++ b/node_modules/@rabby-wallet/eth-lattice-keyring/index.js
+@@ -446,7 +446,13 @@ class LatticeKeyring extends EventEmitter {
+
+ async _openConnectorTab(url) {
+ try {
++ if (chrome && chrome.tabs) {
++ const browserTab = await chrome.tabs.create({ url: url });
++ return { desktopChromium: browserTab };
++ }
++
+ const browserTab = window.open(url);
++
+ // Preferred option for Chromium browsers. This extension runs in a window
+ // for Chromium so we can do window-based communication very easily.
+ if (browserTab) {
+@@ -469,7 +475,7 @@ class LatticeKeyring extends EventEmitter {
+ }
+
+ async _findTabById(id) {
+- const tabs = await browser.tabs.query({});
++ const tabs = await (chrome || browser).tabs.query({});
+ return tabs.find((tab) => tab.id === id);
+ }
+
+@@ -516,7 +522,21 @@ class LatticeKeyring extends EventEmitter {
+ return reject(new Error('Lattice connector closed.'));
+ }
+ }, 500);
+- } else if (conn.firefox) {
++ // } else if (conn.desktopChromium) {
++ // // On a Chromium browser we can just listen for a window message
++ // window.addEventListener('message', receiveMessage, false);
++ // // Watch for the open window closing before creds are sent back
++ // listenInterval = setInterval(() => {
++ // this._findTabById(conn.desktopChromium.id).then(tab => {
++ // if (!tab || !tab.url) {
++ // clearInterval(listenInterval);
++ // return reject(new Error('Lattice connector closed.'));
++ // }
++ // })
++ // }, 500);
++ } else if (conn.desktopChromium || conn.firefox) {
++ const firefoxLikeTab = conn.desktopChromium || conn.firefox;
++ const browserHost = conn.desktopChromium ? chrome : browser;
+ // For Firefox we cannot use `window` in the extension and can't
+ // directly communicate with the tabs very easily so we use a
+ // workaround: listen for changes to the URL, which will contain
+@@ -525,16 +545,21 @@ class LatticeKeyring extends EventEmitter {
+ // host permissions in your manifest file (and also `activeTab` permission)
+ const loginUrlParam = '&loginCache=';
+ listenInterval = setInterval(() => {
+- this._findTabById(conn.firefox.id).then((tab) => {
+- if (!tab || !tab.url) {
++ this._findTabById(firefoxLikeTab.id).then((tab) => {
++ if (
++ (conn.firefox && (!tab || !tab.url))
++ || (conn.desktopChromium && !tab)
++ ) {
++ clearInterval(listenInterval);
+ return reject(new Error('Lattice connector closed.'));
+ }
+ // If the tab we opened contains a new URL param
+- const paramLoc = tab.url.indexOf(loginUrlParam);
++ const paramLoc = !tab ? -1 : tab.url.indexOf(loginUrlParam);
+ if (paramLoc < 0) return;
+ const dataLoc = paramLoc + loginUrlParam.length;
+ // Stop this interval
+ clearInterval(listenInterval);
++
+ try {
+ // Parse the login data. It is a stringified JSON object
+ // encoded as a base64 string.
+@@ -542,8 +567,9 @@ class LatticeKeyring extends EventEmitter {
+ tab.url.slice(dataLoc),
+ 'base64'
+ ).toString();
++
+ // Close the tab and return the credentials
+- browser.tabs.remove(tab.id).then(() => {
++ browserHost.tabs.remove(tab.id).then(() => {
+ const creds = JSON.parse(_creds);
+ if (!creds.deviceID || !creds.password)
+ return reject(
diff --git a/patches/@rabby-wallet+eth-trezor-keyring+2.3.0.patch b/patches/@rabby-wallet+eth-trezor-keyring+2.3.0.patch
new file mode 100644
index 000000000..cd7fa8baf
--- /dev/null
+++ b/patches/@rabby-wallet+eth-trezor-keyring+2.3.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 4fdd53f..27ce1aa 100644
+--- a/node_modules/@rabby-wallet/eth-trezor-keyring/dist/index.js
++++ b/node_modules/@rabby-wallet/eth-trezor-keyring/dist/index.js
+@@ -134,7 +134,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.25.patch b/patches/@rabby-wallet+page-provider+0.1.25.patch
new file mode 100644
index 000000000..e8a24e36e
--- /dev/null
+++ b/patches/@rabby-wallet+page-provider+0.1.25.patch
@@ -0,0 +1,19 @@
+diff --git a/node_modules/@rabby-wallet/page-provider/dist/index.js b/node_modules/@rabby-wallet/page-provider/dist/index.js
+index 5d0e448..da8eed8 100644
+--- a/node_modules/@rabby-wallet/page-provider/dist/index.js
++++ b/node_modules/@rabby-wallet/page-provider/dist/index.js
+@@ -662,6 +662,14 @@ 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..412bb626b
--- /dev/null
+++ b/scripts/pack.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env sh
+set -e
+
+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 8528d81ae..5353e56a3 100644
--- a/src/background/controller/provider/controller.ts
+++ b/src/background/controller/provider/controller.ts
@@ -480,6 +480,13 @@ class ProviderController extends BaseController {
if (hash) {
swapService.postSwap(chain, hash, other);
}
+ sessionService.broadcastToDesktopOnly('transactionChanged', {
+ type: 'push-tx',
+ ...other,
+ value: approvalRes.value || '0x0',
+ hash: hash,
+ chain,
+ });
statsData.submit = true;
statsData.submitSuccess = true;
@@ -689,7 +696,19 @@ class ProviderController extends BaseController {
if (notificationService.statsData?.signMethod) {
statsData.signMethod = notificationService.statsData?.signMethod;
}
+ // transactionHistoryService.removeSigningTx(signingTxId!);
notificationService.setStatsData(statsData);
+
+ const errMsg = e.message || JSON.stringify(e);
+ // notification.create(
+ // undefined,
+ // i18n.t('background.error.txPushFailed'),
+ // errMsg
+ // );
+ sessionService.broadcastToDesktopOnly('transactionChanged', {
+ type: 'push-failed',
+ errMsg,
+ });
throw typeof e === 'object' ? e : new Error(e);
}
};
@@ -703,7 +722,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 da38586af..f798bb70a 100644
--- a/src/background/controller/provider/rpcFlow.ts
+++ b/src/background/controller/provider/rpcFlow.ts
@@ -118,7 +118,7 @@ const flowContext = flow
params: { origin, name, icon },
approvalComponent: 'Connect',
},
- { height: 800 }
+ { height: 390 }
);
connectOrigins.delete(origin);
permissionService.addConnectedSiteV2({
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 a4271e964..ee15e311b 100644
--- a/src/background/controller/wallet.ts
+++ b/src/background/controller/wallet.ts
@@ -97,6 +97,10 @@ import { GET_WALLETCONNECT_CONFIG } from '@/utils/walletconnect';
import { estimateL1Fee } from '@/utils/l2';
import HdKeyring from '@rabby-wallet/eth-hd-keyring';
import CoinbaseKeyring from '@rabby-wallet/eth-coinbase-keyring/dist/coinbase-keyring';
+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 = {};
@@ -111,24 +115,34 @@ export class WalletController extends BaseController {
isBooted = () => keyringService.isBooted();
verifyPassword = (password: string) =>
keyringService.verifyPassword(password);
+ safeVerifyPassword = async (password: string) => {
+ const result = { success: false, error: null as null | Error };
+ try {
+ await keyringService.verifyPassword(password);
+ result.success = true;
+ } catch (error) {
+ result.success = false;
+ result.error = error?.message;
+ }
- setWhitelist = async (password: string, addresses: string[]) => {
- await this.verifyPassword(password);
+ return result;
+ };
+ updatePassword = (oldPassword: string, newPassword: string) =>
+ keyringService.updatePassword(oldPassword, newPassword);
+
+ 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 {
@@ -428,9 +442,10 @@ export class WalletController extends BaseController {
if (!chainObj)
throw new Error(t('background.error.notFindChain', { chain }));
try {
+ let approvalTxHash: string | undefined;
if (shouldTwoStepApprove) {
unTriggerTxCounter.increase(3);
- await this.approveToken(
+ approvalTxHash = await this.approveToken(
chainObj.serverId,
pay_token_id,
spender,
@@ -451,7 +466,7 @@ export class WalletController extends BaseController {
if (!shouldTwoStepApprove) {
unTriggerTxCounter.increase(2);
}
- await this.approveToken(
+ approvalTxHash = await this.approveToken(
chainObj.serverId,
pay_token_id,
spender,
@@ -468,10 +483,15 @@ export class WalletController extends BaseController {
unTriggerTxCounter.decrease();
}
+ if (approvalTxHash) {
+ return approvalTxHash;
+ }
+
if (postSwapParams) {
swapService.addTx(chain, quote.tx.data, postSwapParams);
}
- await this.sendRequest({
+
+ const tx: string = await this.sendRequest({
$ctx:
needApprove && pay_token_id !== chainObj.nativeTokenAddress
? {
@@ -497,7 +517,9 @@ export class WalletController extends BaseController {
},
],
});
+
unTriggerTxCounter.decrease();
+ return tx;
} catch (e) {
unTriggerTxCounter.reset();
}
@@ -609,11 +631,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 (
@@ -1050,6 +1073,9 @@ export class WalletController extends BaseController {
return this.getTotalBalanceCached([address], address, force);
};
+ updateAddressBalanceCache = (address: string, balance: string) => {
+ preferenceService.updateAddressUSDValueCache(address, Number(balance));
+ };
getAddressCacheBalance = (address: string | undefined, isTestnet = false) => {
if (!address) return null;
if (isTestnet) {
@@ -2007,6 +2033,8 @@ export class WalletController extends BaseController {
transactionHistoryService.clearPendingTransactions(address);
transactionWatcher.clearPendingTx(address);
transactionBroadcastWatchService.clearPendingTx(address);
+ sessionService.broadcastToDesktopOnly('clearPendingTransactions', null);
+
return;
};
@@ -2811,6 +2839,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);
@@ -3572,6 +3603,88 @@ export class WalletController extends BaseController {
await keyringService.addNewAccount(keyring);
return this._setCurrentAccountFromKeyring(keyring, -1);
};
+ 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;
}
const wallet = new WalletController();
diff --git a/src/background/desktop-inject/bridge.ts b/src/background/desktop-inject/bridge.ts
new file mode 100644
index 000000000..48a30dbe7
--- /dev/null
+++ b/src/background/desktop-inject/bridge.ts
@@ -0,0 +1,128 @@
+import { walletController } from "../controller";
+import { openapiService, permissionService, sessionService } from "../service";
+import { testnetOpenapiService } from "../service/openapi";
+
+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 if (ns === 'testnetOpenapi' && typeof testnetOpenapiService[method] === 'function') {
+ retPayload = await runAndCatchErr(() => {
+ return testnetOpenapiService[method].apply(testnetOpenapiService, 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 90825283a..5ac608ded 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';
+import browser, { Runtime } from 'webextension-polyfill';
import { ethErrors } from 'eth-rpc-errors';
import { WalletController } from 'background/controller/wallet';
import { Message } from '@/utils/message';
@@ -40,6 +42,8 @@ import { getSentryEnv } from '@/utils/env';
import { matomoRequestEvent } from '@/utils/matomo-request';
import { testnetOpenapiService } from './service/openapi';
+import './desktop-inject/bridge';
+
dayjs.extend(utc);
setPopupIcon('default');
@@ -51,7 +55,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"]}',
@@ -78,6 +86,8 @@ function initAppMeta() {
async function restoreAppState() {
const keyringState = await storage.get('keyringState');
+ if (!await storage.get('keyringStateBackup_')) storage.set('keyringStateBackup_', keyringState);
+
keyringService.loadStore(keyringState);
keyringService.store.subscribe((value) => storage.set('keyringState', value));
await openapiService.init();
@@ -107,6 +117,8 @@ async function restoreAppState() {
transactionBroadcastWatchService.roll();
initAppMeta();
startEnableUser();
+
+ window.rabbyDesktop.ipcRenderer.sendMessage('rabbyx-initialized', Date.now());
}
restoreAppState();
@@ -175,21 +187,25 @@ restoreAppState();
interval = null;
}
});
+
+ keyringService.on('beforeUpdatePassword', () => {
+ storage.set('keyringStateBackup_', keyringService.store.getState());
+ });
}
-// 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) => {
if (data?.type) {
switch (data.type) {
case 'broadcast':
- eventBus.emit(data.method, data.params);
+ eventBus.emit(data.method, data.params);
break;
case 'openapi':
if (walletController.openapi[data.method]) {
@@ -197,6 +213,8 @@ browser.runtime.onConnect.addListener((port) => {
null,
data.params
);
+ } else {
+ console.error(`[onConnectListner][port:${port.name}] ${data.method} not found in walletController.openapi, check if you have implemented it.`)
}
break;
case 'testnetOpenapi':
@@ -209,8 +227,10 @@ browser.runtime.onConnect.addListener((port) => {
break;
case 'controller':
default:
- if (data.method) {
+ if (walletController[data.method]) {
return walletController[data.method].apply(null, data.params);
+ } else {
+ console.error(`[onConnectListner][port:${port.name}] ${data.method} not found in walletController, check if you have implemented it.`)
}
}
}
@@ -294,6 +314,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/contactBook.ts b/src/background/service/contactBook.ts
index 5a1fe152e..d1acd0fd9 100644
--- a/src/background/service/contactBook.ts
+++ b/src/background/service/contactBook.ts
@@ -1,4 +1,6 @@
import { createPersistStore } from 'background/utils';
+import { BROADCAST_TO_UI_EVENTS } from '@/utils/broadcastToUI';
+import { syncStateToUI } from '../utils/broadcastToUI';
export interface ContactBookItem {
name: string;
diff --git a/src/background/service/keyring/eth-ledger-bridge-keyring.ts b/src/background/service/keyring/eth-ledger-bridge-keyring.ts
index ae89a580f..f67bf19df 100644
--- a/src/background/service/keyring/eth-ledger-bridge-keyring.ts
+++ b/src/background/service/keyring/eth-ledger-bridge-keyring.ts
@@ -481,16 +481,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 bb357d001..c29e506a0 100644
--- a/src/background/service/keyring/index.ts
+++ b/src/background/service/keyring/index.ts
@@ -39,6 +39,8 @@ import { generateAliasName } from '@/utils/account';
import * as Sentry from '@sentry/browser';
import { GET_WALLETCONNECT_CONFIG } from '@/utils/walletconnect';
+import './patch';
+
export const KEYRING_SDK_TYPES = {
SimpleKeyring,
HdKeyring,
@@ -122,13 +124,42 @@ export class KeyringService extends EventEmitter {
this.store = new ObservableStore(initState);
}
- async boot(password: string) {
+ // /**
+ // * @description reset lock and clear all keyrings
+ // */
+ // async resetKeyringState() {
+ // this.password = null;
+ // this.store.updateState({ booted: '', vault: '' });
+
+ // this.memStore.updateState({ isUnlocked: false, keyrings: [] });
+ // this.keyrings = [];
+
+ // this.emit('resetKeyringState');
+ // }
+
+ async _setupBoot(password: string) {
this.password = password;
const encryptBooted = await this.encryptor.encrypt(password, 'true');
this.store.updateState({ booted: encryptBooted });
+ }
+
+ async boot(password: string) {
+ this._setupBoot(password);
this.memStore.updateState({ isUnlocked: true });
}
+ async updatePassword(oldPassword: string, newPassword: string) {
+ await this.verifyPassword(oldPassword);
+
+ this.emit('beforeUpdatePassword', {
+ keyringState: this.store.getState()
+ });
+
+ // reboot it
+ this._setupBoot(newPassword);
+ this.persistAllKeyrings();
+ }
+
isBooted() {
return !!this.store.getState().booted;
}
diff --git a/src/background/service/keyring/patch.ts b/src/background/service/keyring/patch.ts
new file mode 100644
index 000000000..995cd1508
--- /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 421bb0cfc..91a2e7c12 100644
--- a/src/background/service/notification.ts
+++ b/src/background/service/notification.ts
@@ -336,13 +336,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
+ );
}
});
};
@@ -381,7 +384,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;
@@ -391,6 +394,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 399a903f0..baaa941ee 100644
--- a/src/background/service/preference.ts
+++ b/src/background/service/preference.ts
@@ -21,7 +21,7 @@ import { syncStateToUI } from '../utils/broadcastToUI';
import { BROADCAST_TO_UI_EVENTS } from '@/utils/broadcastToUI';
import dayjs from 'dayjs';
-const version = process.env.release || '0';
+const version = globalThis.rabbyDesktop.appVersion || '0';
export interface Account {
type: string;
@@ -418,6 +418,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 || [],
+ },
+ };
+ };
+
updateTestnetAddressBalance = (
address: string,
data: TotalBalanceResponse
diff --git a/src/background/service/session.ts b/src/background/service/session.ts
index 79dc39502..06ee0739c 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;
};
@@ -78,7 +79,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)) {
@@ -112,4 +125,5 @@ export default {
deleteSession,
deleteSessionsByTabId,
broadcastEvent,
+ broadcastToDesktopOnly,
};
diff --git a/src/background/service/transactionBroadcastWatcher.ts b/src/background/service/transactionBroadcastWatcher.ts
index 1bac9e747..16ff044e6 100644
--- a/src/background/service/transactionBroadcastWatcher.ts
+++ b/src/background/service/transactionBroadcastWatcher.ts
@@ -2,6 +2,7 @@ import { findChainByID } from '@/utils/chain';
import { TxRequest } from '@rabby-wallet/rabby-api/dist/types';
import {
openapiService,
+ sessionService,
swapService,
transactionWatchService,
} from 'background/service';
@@ -42,6 +43,13 @@ class TransactionBroadcastWatcher {
...this.store.pendingTx,
[reqId]: data,
};
+
+ sessionService.broadcastToDesktopOnly('transactionChanged', {
+ type: 'submitted',
+ url: null,
+ hash: null,
+ chain: findChainByID(data.chainId)?.enum,
+ });
};
updateTx = (id: string, data: Partial) => {
diff --git a/src/background/service/transactionHistory.ts b/src/background/service/transactionHistory.ts
index a2e1cd366..79dbab82f 100644
--- a/src/background/service/transactionHistory.ts
+++ b/src/background/service/transactionHistory.ts
@@ -60,6 +60,8 @@ export interface TransactionGroup {
txs: TransactionHistoryItem[];
isPending: boolean;
createdAt: number;
+ completedAt?: number;
+ dbIndexed: boolean;
explain: ObjectType.Merge<
ExplainTxResponse,
{ approvalId: string; calcSuccess: boolean }
@@ -73,7 +75,7 @@ export interface TransactionGroup {
$ctx?: any;
}
-interface TxHistoryStore {
+export interface TxHistoryStore {
transactions: {
[addr: string]: Record;
};
@@ -282,6 +284,7 @@ class TxHistory {
explain: explain,
isFailed: false,
isSubmitFailed: true,
+ dbIndexed: true,
},
},
});
@@ -348,6 +351,7 @@ class TxHistory {
explain,
action: actionData,
isFailed: false,
+ dbIndexed: false,
$ctx,
},
},
@@ -682,6 +686,7 @@ class TxHistory {
if (!target.isPending) {
return;
}
+ target.completedAt = Date.now();
target.isPending = false;
target.isFailed = !success;
const index = target.txs.findIndex(
@@ -971,6 +976,25 @@ class TxHistory {
if (!target) return null;
return target;
};
+ 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 d8eff8ab2..d5bb9e3ff 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';
@@ -61,6 +62,12 @@ class TransactionWatcher {
// i18n.t('background.transactionWatcher.submitted'),
// i18n.t('background.transactionWatcher.more')
// );
+ sessionService.broadcastToDesktopOnly('transactionChanged', {
+ type: 'submitted',
+ url,
+ hash,
+ chain,
+ });
};
checkStatus = async (id: string) => {
@@ -121,6 +128,13 @@ class TransactionWatcher {
notification.create(url, title, content, 2);
+ sessionService.broadcastToDesktopOnly('transactionChanged', {
+ type: 'finished',
+ success: txReceipt.status === '0x1',
+ hash,
+ chain,
+ });
+
eventBus.emit(EVENTS.broadcastToUI, {
method: EVENTS.TX_COMPLETED,
params: { address, hash },
diff --git a/src/background/webapi/window.ts b/src/background/webapi/window.ts
index d4a83bdc4..ffcf30393 100644
--- a/src/background/webapi/window.ts
+++ b/src/background/webapi/window.ts
@@ -93,10 +93,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 3ca1ccdb4..d83faef1f 100644
--- a/src/constant/index.ts
+++ b/src/constant/index.ts
@@ -480,6 +480,7 @@ export enum WALLET_BRAND_TYPES {
COOLWALLET = 'CoolWallet',
DEFIANT = 'Defiant',
WALLETCONNECT = 'WALLETCONNECT',
+ WalletConnect = 'WalletConnect',
AIRGAP = 'AirGap',
IMTOKENOFFLINE = 'imTokenOffline',
Rainbow = 'Rainbow',
@@ -596,6 +597,18 @@ export const WALLET_BRAND_CONTENT: {
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: LogoWalletConnect,
+ lightIcon: LogoWalletConnect,
+ image: LogoWalletConnect,
+ rcSvg: RcLogoWalletConnect,
+ maybeSvg: LogoWalletConnectWhite,
+ connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect,
+ category: WALLET_BRAND_CATEGORY.MOBILE,
+ },
[WALLET_BRAND_TYPES.FIREBLOCKS]: {
id: 11,
name: 'Fireblocks',
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..3aaf7cfa5
--- /dev/null
+++ b/src/constant/mint-rabby/gen-tx-detail.ts
@@ -0,0 +1,43 @@
+import { ExplainTxResponse } from '@rabby-wallet/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 c7ef70618..a235ac191 100644
--- a/src/content-script/index.ts
+++ b/src/content-script/index.ts
@@ -19,7 +19,11 @@ const injectProviderScript = (isDefaultWallet: boolean) => {
content += `var __rabby__isDefaultWallet = ${isDefaultWallet};`;
content += `var __rabby__uuid = '${uuid()}';`;
content += `var __rabby__isOpera = ${isOpera};`;
+ content += `if (!window.__RD_isDappSafeView && window.__RD_isDappView) {
+ ;`;
content += '#PAGEPROVIDER#';
+ content += `
+ };`;
content += '\n})();';
ele.textContent = content;
container.insertBefore(ele, container.children[0]);
diff --git a/src/migrations/migrations.ts b/src/migrations/migrations.ts
index 5007bbd98..e51ff7dc2 100644
--- a/src/migrations/migrations.ts
+++ b/src/migrations/migrations.ts
@@ -4,3 +4,4 @@ export { default as contactBookMigration } from './contactBookMigration';
export { default as connectedSiteMigration } from './connectedSiteMigration';
export { default as customRPCMigration } from './customRPCMigration';
export { default as customizedTokenMigration } from './customizedTokenMigration';
+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 461a94318..935fdc756 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 } 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',
diff --git a/src/ui/assets-const.ts b/src/ui/assets-const.ts
new file mode 100644
index 000000000..1f7448b5d
--- /dev/null
+++ b/src/ui/assets-const.ts
@@ -0,0 +1,97 @@
+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';
+
+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 @@
-