├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── build ├── _raw │ ├── _locales │ │ └── en │ │ │ └── messages.json │ ├── favicon.ico │ ├── images │ │ ├── icons │ │ │ ├── arrow-left-right.svg │ │ │ ├── arrow-left.svg │ │ │ ├── arrow-right.svg │ │ │ ├── arrow-up-right.svg │ │ │ ├── check.svg │ │ │ ├── circle-check.svg │ │ │ ├── circle-info.svg │ │ │ ├── circle-question.svg │ │ │ ├── clock-solid.svg │ │ │ ├── compass-solid.svg │ │ │ ├── copy-solid.svg │ │ │ ├── delete.svg │ │ │ ├── discord.svg │ │ │ ├── down.svg │ │ │ ├── eye-slash.svg │ │ │ ├── eye.svg │ │ │ ├── gear-solid.svg │ │ │ ├── github.svg │ │ │ ├── grid-solid.svg │ │ │ ├── info.svg │ │ │ ├── kaspa-black.svg │ │ │ ├── kaspa.svg │ │ │ ├── list-solid.svg │ │ │ ├── ordinals.svg │ │ │ ├── pencil.svg │ │ │ ├── qrcode.svg │ │ │ ├── scissors.svg │ │ │ ├── success.svg │ │ │ ├── telegram.svg │ │ │ ├── twitter.svg │ │ │ ├── user-solid.svg │ │ │ ├── wallet-solid.svg │ │ │ ├── warning.svg │ │ │ └── xmark.svg │ │ └── logo │ │ │ ├── logo@128x.png │ │ │ ├── logo@16x.png │ │ │ ├── logo@32x.png │ │ │ ├── logo@48x.png │ │ │ └── wallet-logo.png │ ├── index.html │ ├── manifest │ │ ├── _base_v2.json │ │ ├── _base_v3.json │ │ ├── brave.json │ │ ├── chrome.json │ │ ├── edge.json │ │ └── firefox.json │ └── notification.html ├── init_tranlation_text.js ├── languages.js ├── paths.js ├── plugins │ └── AssetReplacePlugin.js ├── pull_translation.js ├── webpack.common.config.js ├── webpack.debug.config.js ├── webpack.dev.config.js └── webpack.pro.config.js ├── gulpfile.js ├── package.json ├── pnpm-lock.yaml ├── release-notes.md ├── scripts └── fix-modules.js ├── src ├── background │ ├── background.html │ ├── controller │ │ ├── base.ts │ │ ├── index.ts │ │ ├── provider │ │ │ ├── controller.ts │ │ │ ├── index.ts │ │ │ ├── internalMethod.ts │ │ │ └── rpcFlow.ts │ │ └── wallet.ts │ ├── index.ts │ ├── service │ │ ├── contactBook.ts │ │ ├── i18n.ts │ │ ├── index.ts │ │ ├── keyring │ │ │ ├── display.ts │ │ │ └── index.ts │ │ ├── keyringclass │ │ │ ├── hd-keyring.d.ts │ │ │ ├── hd-keyring.ts │ │ │ ├── index.ts │ │ │ └── simple-keyring.ts │ │ ├── notification.ts │ │ ├── openapi.ts │ │ ├── permission.ts │ │ ├── preference.ts │ │ └── session.ts │ ├── utils │ │ ├── index.ts │ │ ├── onekey │ │ │ ├── bip340.ts │ │ │ └── nobleSecp256k1Wrapper.ts │ │ ├── persisitStore.ts │ │ └── promiseFlow.ts │ └── webapi │ │ ├── browser.ts │ │ ├── index.ts │ │ ├── notification.ts │ │ ├── storage.ts │ │ ├── tab.ts │ │ └── window.ts ├── content-script │ ├── index.ts │ └── pageProvider │ │ ├── dedupePromise.ts │ │ ├── index.ts │ │ ├── pushEventHandlers.ts │ │ ├── readyPromise.ts │ │ └── utils.ts ├── shared │ ├── constant │ │ └── index.ts │ ├── eventBus.ts │ ├── lib │ │ └── satsname-utils.ts │ ├── modules.ts │ ├── react-app-env.d.ts │ ├── types.ts │ └── utils │ │ ├── index.ts │ │ └── message │ │ ├── broadcastChannelMessage.ts │ │ ├── index.ts │ │ └── portMessage.ts └── ui │ ├── components │ ├── AccountSelect │ │ ├── index.less │ │ └── index.tsx │ ├── ActionComponent │ │ ├── Loading │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── Tip │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── Toast │ │ │ ├── index.less │ │ │ └── index.tsx │ │ └── index.tsx │ ├── AddressBar │ │ └── index.tsx │ ├── AddressDetailPopover │ │ └── index.tsx │ ├── AddressText │ │ └── index.tsx │ ├── AddressTypeCard │ │ └── index.tsx │ ├── BaseView │ │ ├── index.less │ │ └── index.tsx │ ├── Button │ │ └── index.tsx │ ├── Card │ │ └── index.tsx │ ├── Column │ │ └── index.tsx │ ├── Content │ │ ├── index.less │ │ └── index.tsx │ ├── CopyableAddress │ │ └── index.tsx │ ├── Empty │ │ └── index.tsx │ ├── FeeRateBar │ │ └── index.tsx │ ├── Footer │ │ └── index.tsx │ ├── FooterButtonContainer │ │ └── index.tsx │ ├── Grid │ │ └── index.tsx │ ├── Header │ │ ├── index.module.less │ │ └── index.tsx │ ├── Icon │ │ └── index.tsx │ ├── Iframe.tsx │ ├── Image │ │ └── index.tsx │ ├── Input │ │ ├── index.less │ │ └── index.tsx │ ├── Layout │ │ ├── index.less │ │ └── index.tsx │ ├── Logo │ │ └── index.tsx │ ├── NavTabBar │ │ └── index.tsx │ ├── NoticePopover │ │ └── index.tsx │ ├── OutputValueBar │ │ └── index.tsx │ ├── Pagination │ │ └── index.tsx │ ├── Popover │ │ └── index.tsx │ ├── RefreshButton │ │ └── index.tsx │ ├── RemoveContactPopover │ │ └── index.tsx │ ├── RemoveWalletPopover │ │ └── index.tsx │ ├── Responsive.tsx │ ├── Row │ │ ├── index.less │ │ └── index.tsx │ ├── TabBar │ │ ├── index.less │ │ └── index.tsx │ ├── Text │ │ └── index.tsx │ ├── TextArea │ │ └── index.tsx │ ├── UpgradePopover │ │ └── index.tsx │ ├── WarningPopover │ │ └── index.tsx │ ├── WebsiteBar │ │ └── index.tsx │ └── index.tsx │ ├── features │ └── browser │ │ └── tabs.ts │ ├── index.tsx │ ├── pages │ ├── Account │ │ ├── AddKeyringScreen.tsx │ │ ├── ContactBookScreen.tsx │ │ ├── CreateAccountScreen.tsx │ │ ├── CreateContactScreen.tsx │ │ ├── CreateHDWalletScreen.tsx │ │ ├── CreatePasswordScreen.tsx │ │ ├── CreateSimpleWalletScreen.tsx │ │ ├── SwitchAccountScreen.tsx │ │ ├── SwitchKeyringScreen.tsx │ │ └── UnlockScreen │ │ │ └── index.tsx │ ├── Approval │ │ ├── ApprovalScreen.tsx │ │ ├── ConnectedSitesScreen.tsx │ │ └── components │ │ │ ├── Connect.tsx │ │ │ ├── MultiSignPsbt │ │ │ └── index.tsx │ │ │ ├── SignPsbt │ │ │ └── index.tsx │ │ │ ├── SignText.tsx │ │ │ ├── SwitchNetwork.tsx │ │ │ └── index.ts │ ├── Main │ │ ├── AppTabScreen.tsx │ │ ├── BoostScreen.tsx │ │ ├── DiscoverTabScreen.tsx │ │ ├── SettingsTabScreen.tsx │ │ ├── WalletTabScreen.tsx │ │ └── WelcomeScreen.tsx │ ├── MainRoute.tsx │ ├── Settings │ │ ├── AddressTypeScreen.tsx │ │ ├── ChangePasswordScreen.tsx │ │ ├── EditAccountNameScreen.tsx │ │ ├── EditContactNameScreen.tsx │ │ ├── EditNetworkUrlScreen.tsx │ │ ├── EditWalletNameScreen.tsx │ │ ├── ExportMnemonicsScreen.tsx │ │ ├── ExportPrivateKeyScreen.tsx │ │ ├── LanguageTypeScreen.tsx │ │ ├── NetworkTypeScreen.tsx │ │ └── UpgradeNoticeScreen.tsx │ ├── Test │ │ └── TestScreen.tsx │ ├── Wallet │ │ ├── FiatPayScreen.tsx │ │ ├── ReceiveScreen │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── TxConfirmScreen.tsx │ │ ├── TxCreateScreen.tsx │ │ ├── TxDetailScreen.tsx │ │ ├── TxFailScreen.tsx │ │ ├── TxSuccessScreen.tsx │ │ └── UtxoDetailScreen.tsx │ └── index.module.less │ ├── state │ ├── accounts │ │ ├── hooks.ts │ │ ├── reducer.ts │ │ └── updater.tsx │ ├── global │ │ ├── actions.ts │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── hooks.ts │ ├── index.ts │ ├── keyrings │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── settings │ │ ├── hooks.ts │ │ └── reducer.ts │ └── transactions │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── styles │ ├── font.css │ ├── fonts │ │ ├── Inter-Light.woff2 │ │ ├── Inter-Regular.woff2 │ │ ├── Inter-SemiBold.woff2 │ │ ├── ProtoMono-Bold.otf │ │ ├── ProtoMono-Light.otf │ │ └── ProtoMono-Regular.otf │ └── global.less │ ├── theme │ ├── colors.ts │ ├── font.ts │ ├── spacing.ts │ └── typography.ts │ └── utils │ ├── WalletContext.tsx │ ├── hooks.ts │ ├── i18n.ts │ └── index.ts ├── tailwind.config.js ├── tsconfig.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /src/kaspaweb/** 2 | 3 | /build/** 4 | /coverage/** 5 | /docs/* 6 | !/docs/*.js 7 | !/docs/tools/ 8 | /jsdoc/** 9 | /templates/** 10 | /tests/bench/** 11 | /tests/fixtures/** 12 | /tests/performance/** 13 | /tmp/** -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | jest: true 6 | }, 7 | extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | ecmaFeatures: { 11 | jsx: true 12 | }, 13 | ecmaVersion: 'latest', 14 | sourceType: 'module' 15 | }, 16 | plugins: ['react', 'react-hooks', '@typescript-eslint', 'import', 'simple-import-sort', 'unused-imports'], 17 | rules: { 18 | /** 19 | * off or 0:Indicates that the rule is not validated. 20 | * warn or 1:Indicates the validation rule, when not satisfied, give a warning 21 | * error or 2 :Indicates that the validation rules are not met, and an error is reported if they are not satisfied. 22 | */ 23 | quotes: [1, 'single'], 24 | // "no-console": process.env.NODE_ENV === 'production' ? 2 : 0, // do not disable the console 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, // disable debugger 26 | semi: 0, 27 | 'no-control-regex': 2, 28 | 'linebreak-style': [0, 'error', 'windows'], 29 | indent: ['error', 2, { SwitchCase: 1 }], 30 | 'array-bracket-spacing': [2, 'never'], 31 | 'no-irregular-whitespace': 0, 32 | 'no-trailing-spaces': 1, 33 | 'eol-last': 0, 34 | 'no-unused-vars': [1, { vars: 'all', args: 'after-used' }], 35 | 'no-underscore-dangle': 0, 36 | 'no-lone-blocks': 0, 37 | 'no-class-assign': 2, 38 | 'no-floating-decimal': 2, 39 | 'no-loop-func': 1, 40 | 'no-cond-assign': 2, 41 | 'no-delete-var': 2, 42 | 'no-dupe-keys': 2, 43 | 'no-duplicate-case': 2, 44 | 'no-dupe-args': 2, 45 | 'no-empty': 2, 46 | 'no-func-assign': 2, 47 | 'no-invalid-this': 0, 48 | 'no-this-before-super': 0, 49 | 'no-undef': 1, 50 | 'no-use-before-define': 0, 51 | camelcase: 0, 52 | '@typescript-eslint/no-var-requires': 0, 53 | 54 | 'react/display-name': 0, 55 | 'react/react-in-jsx-scope': 0, 56 | 'react/no-unescaped-entities': 0, 57 | 'unused-imports/no-unused-imports': 'warn', 58 | '@typescript-eslint/no-unused-vars': [ 59 | 'warn', 60 | { 61 | argsIgnorePattern: '^_', 62 | varsIgnorePattern: '^_' 63 | } 64 | ] 65 | }, 66 | settings: { 67 | 'import/resolver': { 68 | typescript: {} 69 | }, 70 | react: { 71 | version: 'detect' 72 | } 73 | }, 74 | overrides: [ 75 | { 76 | files: ['**/*.tsx'], 77 | rules: { 78 | 'react/prop-types': 'off' 79 | } 80 | } 81 | ] 82 | }; 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # kaspa wasm 9 | /src/kaspaweb 10 | 11 | # testing 12 | /coverage 13 | 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | dist/ 26 | .key.config.js 27 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "useTabs": false, 6 | "semi": true, 7 | "vueIndentScriptAndStyle": true, 8 | "trailingComma": "none", 9 | "bracketSpacing": true, 10 | "jsxBracketSameLine": true, 11 | "arrowParens": "always", 12 | "requirePragma": false, 13 | "insertPragma": false, 14 | "importOrder": ["^@formily/(.*)", "^@(.*)$", "^[./]"], 15 | "importOrderSeparation": true 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.organizeImports": "explicit", 4 | "source.fixAll.eslint": "explicit" 5 | }, 6 | "editor.formatOnSave": false, 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "[json]": { 9 | "editor.defaultFormatter": "vscode.json-language-features" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024-present KasWare 4 | 5 | Brand name and logo of KasWare is copyright reserved. You can not use brand name or logo of KasWare for your re-publish software. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KasWare Wallet 2 | 3 | KasWare Wallet - the first open-source browser extension wallet for Kaspa. 4 | 5 | - Website: https://kasware.xyz/ 6 | - Twitter: https://twitter.com/kasware_wallet 7 | 8 | ## How to build 9 | 10 | - Install [Node.js](https://nodejs.org) version 18 11 | - Install [pnpm] 12 | - Install dependencies: `pnpm install` 13 | - Build the project to the `./dist/` folder with `pnpm run build:firefox` for Firefox 14 | - Build the project to the `./dist/` folder with `pnpm run build:chrome` for Chrome 15 | - Develop: `pnpm run build:chrome:dev` 16 | 17 | ## Special Thanks 18 | 19 | Thanks to the MetaMask team and Unisat team for their contributions to the browser extension wallet community, KasWare Wallet relies heavily on their contributions. 20 | 21 | Wallet feature is powered by kaspa-wasm module developed by kaspa core team. And the default RPC services utilized by Kasware Wallet are expertly maintained by the Aspectron team, the masterminds behind the KDX Wallet, Kaspanet Web Wallet, and the Kaspa-NG project. -------------------------------------------------------------------------------- /build/_raw/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/build/_raw/favicon.ico -------------------------------------------------------------------------------- /build/_raw/images/icons/arrow-left-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/arrow-up-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/circle-check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/circle-info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/circle-question.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/clock-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/compass-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /build/_raw/images/icons/copy-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /build/_raw/images/icons/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /build/_raw/images/icons/eye-slash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/gear-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/grid-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build/_raw/images/icons/kaspa-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /build/_raw/images/icons/kaspa.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /build/_raw/images/icons/list-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/ordinals.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /build/_raw/images/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/qrcode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/scissors.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build/_raw/images/icons/telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/icons/user-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /build/_raw/images/icons/wallet-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build/_raw/images/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build/_raw/images/icons/xmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /build/_raw/images/logo/logo@128x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/build/_raw/images/logo/logo@128x.png -------------------------------------------------------------------------------- /build/_raw/images/logo/logo@16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/build/_raw/images/logo/logo@16x.png -------------------------------------------------------------------------------- /build/_raw/images/logo/logo@32x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/build/_raw/images/logo/logo@32x.png -------------------------------------------------------------------------------- /build/_raw/images/logo/logo@48x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/build/_raw/images/logo/logo@48x.png -------------------------------------------------------------------------------- /build/_raw/images/logo/wallet-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/build/_raw/images/logo/wallet-logo.png -------------------------------------------------------------------------------- /build/_raw/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | KasWare Wallet 12 | 13 | 14 | 15 |
16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /build/_raw/manifest/_base_v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_appName__", 4 | "version": "1.0", 5 | 6 | "default_locale": "en", 7 | "description": "__MSG_appDescription__", 8 | "icons": { 9 | "16": "/images/logo/logo@16x.png", 10 | "32": "/images/logo/logo@32x.png", 11 | "48": "/images/logo/logo@48x.png", 12 | "128": "/images/logo/logo@128x.png" 13 | }, 14 | 15 | "browser_action": { 16 | "default_popup": "index.html", 17 | "default_icon": { 18 | "16": "/images/logo/logo@16x.png", 19 | "32": "/images/logo/logo@32x.png", 20 | "48": "/images/logo/logo@48x.png", 21 | "128": "/images/logo/logo@128x.png" 22 | }, 23 | "default_title": "__MSG_appName__" 24 | }, 25 | 26 | "author": "https://kasware.xyz", 27 | "background": { 28 | "page": "background.html", 29 | "persistent": true 30 | }, 31 | "homepage_url": "https://kasware.xyz", 32 | "permissions": ["storage", "unlimitedStorage", "activeTab"], 33 | "short_name": "__MSG_appName__", 34 | 35 | "content_scripts": [ 36 | { 37 | "matches": [""], 38 | "js": ["content-script.js"], 39 | "run_at": "document_start", 40 | "all_frames": true 41 | } 42 | ], 43 | "content_security_policy": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", 44 | "web_accessible_resources": ["pageProvider.js"] 45 | } 46 | -------------------------------------------------------------------------------- /build/_raw/manifest/_base_v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "__MSG_appName__", 4 | "version": "1.0", 5 | 6 | "default_locale": "en", 7 | "description": "__MSG_appDescription__", 8 | "icons": { 9 | "16": "/images/logo/logo@16x.png", 10 | "32": "/images/logo/logo@32x.png", 11 | "48": "/images/logo/logo@48x.png", 12 | "128": "/images/logo/logo@128x.png" 13 | }, 14 | 15 | "action": { 16 | "default_popup": "index.html", 17 | "default_icon": { 18 | "16": "/images/logo/logo@16x.png", 19 | "32": "/images/logo/logo@32x.png", 20 | "48": "/images/logo/logo@48x.png", 21 | "128": "/images/logo/logo@128x.png" 22 | }, 23 | "default_title": "__MSG_appName__" 24 | }, 25 | 26 | "author": "https://kasware.xyz", 27 | "background": { 28 | "service_worker": "background.js" 29 | }, 30 | "homepage_url": "https://kasware.xyz", 31 | "permissions": ["storage", "unlimitedStorage", "activeTab"], 32 | "short_name": "__MSG_shortName__", 33 | 34 | "content_scripts": [ 35 | { 36 | "matches": [""], 37 | "js": ["content-script.js"], 38 | "run_at": "document_start", 39 | "all_frames": true 40 | } 41 | ], 42 | 43 | "web_accessible_resources": [ 44 | { 45 | "resources": ["pageProvider.js"], 46 | "matches": [""] 47 | } 48 | ], 49 | "content_security_policy": { 50 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /build/_raw/manifest/brave.json: -------------------------------------------------------------------------------- 1 | { 2 | "externally_connectable": { 3 | "matches": ["https://kasware.xyz/*"], 4 | "ids": ["*"] 5 | }, 6 | "minimum_chrome_version": "66" 7 | } 8 | -------------------------------------------------------------------------------- /build/_raw/manifest/chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "externally_connectable": { 3 | "matches": ["https://kasware.xyz/*"], 4 | "ids": ["*"] 5 | }, 6 | "minimum_chrome_version": "88" 7 | } 8 | -------------------------------------------------------------------------------- /build/_raw/manifest/edge.json: -------------------------------------------------------------------------------- 1 | { 2 | "externally_connectable": { 3 | "matches": ["https://kasware.xyz/*"], 4 | "ids": ["*"] 5 | }, 6 | "minimum_chrome_version": "66" 7 | } 8 | -------------------------------------------------------------------------------- /build/_raw/manifest/firefox.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /build/_raw/notification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | KasWare Wallet 12 | 13 | 14 | 15 |
16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /build/init_tranlation_text.js: -------------------------------------------------------------------------------- 1 | const { Client } = require('@notionhq/client'); 2 | const { notionKey } = require('../.key.config.js'); 3 | const { token } = notionKey; 4 | const notion = new Client({ auth: token }); 5 | const trans = require('./_raw/_locales/en/messages.json') 6 | 7 | const run = async (trans) => { 8 | for (const [key, value] of Object.entries(trans)) { 9 | console.log(`${key}`); 10 | const obj = { 11 | parent: { 12 | database_id: '60da59ea394e4ad8a69cc308112ec323', 13 | }, 14 | properties: { 15 | 'Name': { 16 | type: 'title', 17 | title: [ 18 | { 19 | type: 'text', 20 | text: { 21 | content: key, 22 | }, 23 | }, 24 | ], 25 | }, 26 | 27 | }, 28 | } 29 | const response = await notion.pages.create(obj); 30 | // console.log(response); 31 | } 32 | 33 | 34 | }; 35 | 36 | run(trans) -------------------------------------------------------------------------------- /build/languages.js: -------------------------------------------------------------------------------- 1 | exports.Languages = [ 2 | { name: 'English', symbol: 'en', messages: {} }, 3 | // { name: 'Español', symbol: 'es', messages: {} }, 4 | // { name: 'Francés', symbol: 'fr', messages: {} }, 5 | // { name: '日本語', symbol: 'ja', messages: {} }, 6 | // { name: '한국인', symbol: 'ko', messages: {} }, 7 | { name: '中文(简体)', symbol: 'zh_CN', messages: {} }, 8 | { name: '中文(正體)', symbol: 'zh_TW', messages: {} }, 9 | 10 | ] -------------------------------------------------------------------------------- /build/paths.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const appRoot = fs.realpathSync(process.cwd()); 4 | const rootResolve = path.resolve.bind(path, appRoot); 5 | const buildPath = 'dist'; 6 | 7 | function getBrowserPaths(browser) { 8 | let ret = { 9 | root: appRoot, 10 | src: rootResolve('src'), 11 | indexHtml: rootResolve('build/_raw/index.html'), 12 | notificationHtml: rootResolve('build/_raw/notification.html'), 13 | backgroundHtml: rootResolve('src/background/background.html'), 14 | dist: rootResolve('dist/' + browser), 15 | rootResolve, 16 | dotenv: rootResolve('.env'), 17 | appPath: rootResolve('.'), 18 | appBuild: rootResolve(buildPath), 19 | appPublic: rootResolve('public'), 20 | appHtml: rootResolve('public/index.html'), 21 | appPackageJson: rootResolve('package.json'), 22 | appSrc: rootResolve('src'), 23 | appTsConfig: rootResolve('tsconfig.json'), 24 | appJsConfig: rootResolve('jsconfig.json'), 25 | yarnLockFile: rootResolve('yarn.lock'), 26 | proxySetup: rootResolve('src/setupProxy.js'), 27 | appNodeModules: rootResolve('node_modules'), 28 | appWebpackCache: rootResolve('node_modules/.cache'), 29 | appTsBuildInfoFile: rootResolve('node_modules/.cache/tsconfig.tsbuildinfo') 30 | // publicUrlOrPath, 31 | }; 32 | return ret; 33 | } 34 | 35 | module.exports = { 36 | getBrowserPaths 37 | }; 38 | -------------------------------------------------------------------------------- /build/plugins/AssetReplacePlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * replace string with other assets content at "afterProcessAssets" 3 | */ 4 | 5 | class AssetReplacePlugin { 6 | constructor(options) { 7 | this.options = options; 8 | } 9 | apply(compiler) { 10 | const { 11 | webpack: { 12 | sources: { RawSource } 13 | } 14 | } = compiler; 15 | compiler.hooks.make.tapAsync('AssetReplacePlugin', (compilation, callback) => { 16 | compilation.hooks.afterProcessAssets.tap('AssetReplacePlugin', () => { 17 | const replaceArr = Object.entries(this.options) 18 | .map(([k, v]) => { 19 | let assetName; 20 | for (const chunk of compilation.chunks.values()) { 21 | if (chunk.name === v) { 22 | assetName = chunk.files.values().next().value; 23 | 24 | break; 25 | } 26 | } 27 | return [k, assetName]; 28 | }) 29 | .filter(([, assetName]) => assetName); 30 | 31 | const replaceFn = replaceArr 32 | .map(([k, assetName]) => { 33 | // github.com/webpack/webpack-sources/blob/master/lib/ConcatSource.js 34 | const content = compilation.assets[assetName]?.source(); 35 | 36 | return (source) => { 37 | return source.split(new RegExp(`['"]?${k}['"]?`)).join(JSON.stringify(content)); 38 | }; 39 | }) 40 | .reduce((m, n) => (content) => n(m(content))); 41 | 42 | for (const chunk of compilation.chunks.values()) { 43 | const fileName = chunk.files.values().next().value; 44 | if (!replaceArr.includes(([, assetName]) => assetName === fileName)) { 45 | compilation.updateAsset(fileName, (content) => { 46 | const result = replaceFn(content.source()); 47 | 48 | return new RawSource(result); 49 | }); 50 | } 51 | } 52 | }); 53 | callback(); 54 | }); 55 | } 56 | } 57 | 58 | module.exports = AssetReplacePlugin; 59 | -------------------------------------------------------------------------------- /build/pull_translation.js: -------------------------------------------------------------------------------- 1 | const { Client } = require('@notionhq/client'); 2 | const fs = require('fs'); 3 | const { notionKey } = require('../.key.config.js'); 4 | const { Languages } = require('./languages.js'); 5 | const { token, database_id } = notionKey; 6 | let notion = new Client({ auth: token }); 7 | 8 | function getPropertyPlainText(property) { 9 | let arr = property.title || property.rich_text; 10 | if (arr.length == 0) return ''; 11 | return arr[0].plain_text; 12 | } 13 | 14 | let run = async () => { 15 | let langMap = {}; 16 | Languages.forEach((v) => { 17 | langMap[v.name] = v; 18 | }); 19 | let data = await notion.databases.query({ 20 | database_id 21 | }); 22 | data.results.forEach((v) => { 23 | Languages.forEach((lang) => { 24 | let key = getPropertyPlainText(v.properties['Name']); 25 | if (key) { 26 | lang.messages[key] = { 27 | message: getPropertyPlainText(v.properties[lang.name]) 28 | }; 29 | } else { 30 | console.error('Invalid Key!', key); 31 | } 32 | }); 33 | }); 34 | Languages.forEach((lang) => { 35 | fs.mkdirSync(`./build/_raw/_locales/${lang.symbol}/`, { recursive: true }); 36 | fs.writeFileSync(`./build/_raw/_locales/${lang.symbol}/messages.json`, JSON.stringify(lang.messages)); 37 | }); 38 | }; 39 | run(); 40 | -------------------------------------------------------------------------------- /build/webpack.debug.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | const config = { 4 | mode: 'production', 5 | devtool: false, 6 | performance: { 7 | maxEntrypointSize: 2500000, 8 | maxAssetSize: 2500000 9 | }, 10 | plugins: [ 11 | // new BundleAnalyzerPlugin(), 12 | new webpack.DefinePlugin({ 13 | 'process.env.BUILD_ENV': JSON.stringify('PRO'), 14 | 'process.env.DEBUG': true 15 | }) 16 | ] 17 | }; 18 | 19 | module.exports = config; 20 | -------------------------------------------------------------------------------- /build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | // for extension local test, can build each time 4 | const config = { 5 | mode: 'development', 6 | devtool: 'inline-cheap-module-source-map', 7 | // devtool: 'inline-nosources-cheap-module-source-map', 8 | watch: true, 9 | watchOptions: { 10 | ignored: ['**/public', '**/node_modules'], 11 | followSymlinks: false 12 | }, 13 | plugins: [ 14 | new webpack.DefinePlugin({ 15 | 'process.env.BUILD_ENV': JSON.stringify('DEV'), 16 | 'process.env.DEBUG': true, 17 | 'process.env.TAILWIND_MODE': 'watch' 18 | }) 19 | ] 20 | }; 21 | 22 | module.exports = config; 23 | -------------------------------------------------------------------------------- /build/webpack.pro.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const { EsbuildPlugin } = require('esbuild-loader') 3 | 4 | const config = { 5 | mode: 'production', 6 | devtool: false, 7 | performance: { 8 | maxEntrypointSize: 2500000, 9 | maxAssetSize: 2500000 10 | }, 11 | plugins: [ 12 | // new BundleAnalyzerPlugin(), 13 | new webpack.DefinePlugin({ 14 | 'process.env.BUILD_ENV': JSON.stringify('PRO') 15 | }) 16 | ], 17 | optimization: { 18 | // minimize: false, 19 | minimizer: [ 20 | new EsbuildPlugin({ 21 | keepNames: true, 22 | }), 23 | ] 24 | }, 25 | }; 26 | 27 | module.exports = config; 28 | -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | # KasWare Wallet Release Notes 2 | 3 | ## v0.6.0 4 | 5 | - updated kaspa-wasm from v0.13.1 to RC v0.14.1 6 | 7 | ## v0.5.0 8 | 9 | - display fiat value of balance 10 | - redesign the UI of setting priority fee 11 | - support the import of OKX Wallet and Ledger seed phrase 12 | - automatically scan for addresses 13 | - clearly show tx fees prior to sending a tx. 14 | - show balance on both the wallet and account list pages. 15 | - fix minor issues and improve overall system performance 16 | - optimize UI 17 | 18 | ## v0.4.0 19 | 20 | - add compound TX function 21 | - add sendall function 22 | - support for i18n 23 | - support the creation of 24 mnemonic wallets 24 | - optimize UI 25 | 26 | ## v0.3.0 27 | 28 | - support Onekey Wallet 29 | - fix display issue 30 | - optimize UI 31 | 32 | ## v0.2.8 33 | 34 | - fixed scan more addresses issue 35 | - added some tips 36 | - fixed compile-time warnings 37 | - added esbuild 38 | -------------------------------------------------------------------------------- /scripts/fix-modules.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const fixWindowError = () => { 3 | const file = './node_modules/bitcore-lib/lib/crypto/random.js'; 4 | let fileData = fs.readFileSync(file).toString(); 5 | fileData = fileData.replace( 6 | `Random.getRandomBufferBrowser = function(size) { 7 | if (!window.crypto && !window.msCrypto) 8 | throw new Error('window.crypto not available'); 9 | 10 | if (window.crypto && window.crypto.getRandomValues) 11 | var crypto = window.crypto; 12 | else if (window.msCrypto && window.msCrypto.getRandomValues) //internet explorer 13 | var crypto = window.msCrypto; 14 | else 15 | throw new Error('window.crypto.getRandomValues not available'); 16 | 17 | var bbuf = new Uint8Array(size); 18 | crypto.getRandomValues(bbuf); 19 | var buf = Buffer.from(bbuf); 20 | 21 | return buf; 22 | };`, 23 | `Random.getRandomBufferBrowser = function(size) { 24 | var bbuf = new Uint8Array(size); 25 | crypto.getRandomValues(bbuf); 26 | var buf = Buffer.from(bbuf); 27 | 28 | return buf; 29 | };` 30 | ); 31 | fs.writeFileSync(file, fileData); 32 | }; 33 | 34 | const fixWindowError2 = () => { 35 | const file = './node_modules/tiny-secp256k1/lib/rand.browser.js'; 36 | let fileData = fs.readFileSync(file).toString(); 37 | fileData = fileData.replace('window.crypto', 'crypto'); 38 | fs.writeFileSync(file, fileData); 39 | }; 40 | 41 | 42 | const fixDocumentError = () => { 43 | const file = './dist/chrome/background.js'; 44 | let fileData = fs.readFileSync(file).toString(); 45 | fileData = fileData.replace('document.baseURI || self.location.href', 'self.location.href'); 46 | fileData = fileData.replace('document.baseURI||self.location.href', 'self.location.href'); 47 | fs.writeFileSync(file, fileData); 48 | }; 49 | 50 | const run = async () => { 51 | let success = true; 52 | try { 53 | // fixWindowError(); 54 | // fixWindowError2(); 55 | // fixWindowError3(); 56 | // fixBufferError(); 57 | // fixWalletSdkError(); 58 | fixDocumentError(); 59 | } catch (e) { 60 | console.error('error:', e.message); 61 | success = false; 62 | } finally { 63 | console.log('Fix modules result: ', success ? 'success' : 'failed'); 64 | } 65 | }; 66 | 67 | run(); 68 | -------------------------------------------------------------------------------- /src/background/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/background/controller/base.ts: -------------------------------------------------------------------------------- 1 | class BaseController { 2 | // getCurrentAccount = async () => { 3 | // let account = preferenceService.getCurrentAccount(); 4 | // if (account) { 5 | // const accounts = await this.getAccounts(); 6 | // const matchAcct = accounts.find((acct) => account!.address === acct.address); 7 | // if (!matchAcct) account = undefined; 8 | // } 9 | // if (!account) { 10 | // [account] = await this.getAccounts(); 11 | // if (!account) return null; 12 | // preferenceService.setCurrentAccount(account); 13 | // } 14 | // return cloneDeep(account) as Account; 15 | // }; 16 | // syncGetCurrentAccount = () => { 17 | // return preferenceService.getCurrentAccount() || null; 18 | // }; 19 | // getAccounts = (): Promise => { 20 | // return keyringService.getAllVisibleAccountsArray(); 21 | // }; 22 | } 23 | 24 | export default BaseController; 25 | -------------------------------------------------------------------------------- /src/background/controller/index.ts: -------------------------------------------------------------------------------- 1 | export { default as walletController } from './wallet'; 2 | export { default as providerController } from './provider'; 3 | -------------------------------------------------------------------------------- /src/background/controller/provider/index.ts: -------------------------------------------------------------------------------- 1 | import { ethErrors } from 'eth-rpc-errors'; 2 | 3 | import { sessionService, keyringService } from '@/background/service'; 4 | import { tab } from '@/background/webapi'; 5 | 6 | import internalMethod from './internalMethod'; 7 | import rpcFlow from './rpcFlow'; 8 | 9 | tab.on('tabRemove', (id) => { 10 | sessionService.deleteSession(id); 11 | }); 12 | 13 | export default async (req) => { 14 | const { 15 | data: { method } 16 | } = req; 17 | 18 | if (internalMethod[method]) { 19 | return internalMethod[method](req); 20 | } 21 | 22 | const hasVault = keyringService.hasVault(); 23 | if (!hasVault) { 24 | throw ethErrors.provider.userRejectedRequest({ 25 | message: 'wallet must has at least one account' 26 | }); 27 | } 28 | return rpcFlow(req); 29 | }; 30 | -------------------------------------------------------------------------------- /src/background/controller/provider/internalMethod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { keyringService } from '@/background/service'; 4 | 5 | import wallet from '../wallet'; 6 | 7 | const tabCheckin = ({ 8 | data: { 9 | params: { origin, name, icon } 10 | }, 11 | session 12 | }) => { 13 | session.setProp({ origin, name, icon }); 14 | }; 15 | 16 | const getProviderState = async (req) => { 17 | const { 18 | session: { origin } 19 | } = req; 20 | 21 | const isUnlocked = keyringService.memStore.getState().isUnlocked; 22 | const accounts: string[] = []; 23 | if (isUnlocked) { 24 | const currentAccount = await wallet.getCurrentAccount(); 25 | if (currentAccount) { 26 | accounts.push(currentAccount.address); 27 | } 28 | } 29 | return { 30 | network: wallet.getNetworkName(), 31 | isUnlocked, 32 | accounts 33 | }; 34 | }; 35 | 36 | const keepAlive = () => { 37 | return 'ACK_KEEP_ALIVE_MESSAGE'; 38 | }; 39 | 40 | export default { 41 | tabCheckin, 42 | getProviderState, 43 | keepAlive 44 | }; 45 | -------------------------------------------------------------------------------- /src/background/service/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | 3 | export const fetchLocale = async (locale) => { 4 | const res = await fetch(`./_locales/${locale}/messages.json`); 5 | const data: Record = await res.json(); 6 | return Object.keys(data).reduce((res, key) => { 7 | return { 8 | ...res, 9 | [key.replace(/__/g, ' ')]: data[key].message 10 | }; 11 | }, {}); 12 | }; 13 | 14 | i18n.init({ 15 | fallbackLng: 'en', 16 | defaultNS: 'translations', 17 | interpolation: { 18 | escapeValue: false // react already safes from xss 19 | } 20 | }); 21 | 22 | export const I18N_NS = 'translations'; 23 | 24 | export const addResourceBundle = async (locale: string) => { 25 | if (i18n.hasResourceBundle(locale, I18N_NS)) return; 26 | const bundle = await fetchLocale(locale); 27 | 28 | i18n.addResourceBundle(locale, 'translations', bundle); 29 | }; 30 | 31 | addResourceBundle('en'); 32 | 33 | i18n.on('languageChanged', function (lng) { 34 | addResourceBundle(lng); 35 | }); 36 | 37 | export default i18n; 38 | -------------------------------------------------------------------------------- /src/background/service/index.ts: -------------------------------------------------------------------------------- 1 | export { default as contactBookService } from './contactBook'; 2 | export { default as i18n } from './i18n'; 3 | export { default as keyringService } from './keyring'; 4 | export { default as openapiService } from './openapi'; 5 | export { default as preferenceService } from './preference'; 6 | export { default as notificationService } from './notification'; 7 | export { default as permissionService } from './permission'; 8 | export { default as sessionService } from './session'; 9 | -------------------------------------------------------------------------------- /src/background/service/keyring/display.ts: -------------------------------------------------------------------------------- 1 | import KeyringService, { Keyring } from './index'; 2 | 3 | class DisplayKeyring { 4 | accounts: string[] = []; 5 | type = ''; 6 | hdPath = ''; 7 | 8 | constructor(keyring: Keyring) { 9 | this.accounts = keyring.accounts || []; 10 | this.type = keyring.type; 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 12 | this.hdPath = (keyring as any).hdPath; 13 | } 14 | 15 | unlock = async (): Promise => { 16 | const keyring = await KeyringService.getKeyringForAccount(this.accounts[0], this.type); 17 | if (keyring.unlock) await keyring.unlock(); 18 | }; 19 | 20 | getFirstPage = async () => { 21 | const keyring = await KeyringService.getKeyringForAccount(this.accounts[0], this.type); 22 | if (keyring.getFirstPage) { 23 | return await keyring.getFirstPage(); 24 | } else { 25 | return []; 26 | } 27 | }; 28 | 29 | getNextPage = async () => { 30 | const keyring = await KeyringService.getKeyringForAccount(this.accounts[0], this.type); 31 | if (keyring.getNextPage) { 32 | return await keyring.getNextPage(); 33 | } else { 34 | return []; 35 | } 36 | }; 37 | 38 | getAccounts = async () => { 39 | const keyring = await KeyringService.getKeyringForAccount(this.accounts[0], this.type); 40 | return await keyring.getAccounts(); 41 | }; 42 | 43 | activeAccounts = async (indexes: number[]): Promise => { 44 | const keyring = await KeyringService.getKeyringForAccount(this.accounts[0], this.type); 45 | if (keyring.activeAccounts) { 46 | return keyring.activeAccounts(indexes); 47 | } else { 48 | return []; 49 | } 50 | }; 51 | } 52 | 53 | export default DisplayKeyring; 54 | -------------------------------------------------------------------------------- /src/background/service/keyringclass/hd-keyring.d.ts: -------------------------------------------------------------------------------- 1 | import { SimpleKeyring } from './simple-keyring'; 2 | interface DeserializeOption { 3 | hdPath?: string; 4 | mnemonic?: string; 5 | xpriv?: string; 6 | activeIndexes?: number[]; 7 | passphrase?: string; 8 | } 9 | export declare class HdKeyring extends SimpleKeyring { 10 | static type: string; 11 | type: string; 12 | mnemonic: string; 13 | xpriv: string; 14 | passphrase: string; 15 | network: any; 16 | hdPath: string; 17 | root: any; 18 | hdWallet?: any; 19 | // wallets: ECPairInterface[]; 20 | wallets: any[]; 21 | private _index2wallet; 22 | activeIndexes: number[]; 23 | page: number; 24 | perPage: number; 25 | constructor(opts?: DeserializeOption); 26 | serialize(): Promise; 27 | deserialize(_opts?: DeserializeOption): Promise; 28 | initFromXpriv(xpriv: string): void; 29 | initFromMnemonic(mnemonic: string): void; 30 | changeHdPath(hdPath: string): void; 31 | getAccountByHdPath(hdPath: string, index: number): string; 32 | addAccounts(numberOfAccounts?: number,dType?:number,startIndex?:number): Promise; 33 | activeAccounts(indexes: number[]): string[]; 34 | getFirstPage(): Promise<{ 35 | address: string; 36 | index: number; 37 | }[]>; 38 | getNextPage(): Promise<{ 39 | address: string; 40 | index: number; 41 | }[]>; 42 | getPreviousPage(): Promise<{ 43 | address: string; 44 | index: number; 45 | }[]>; 46 | getAddresses(start: number, end: number): { 47 | address: string; 48 | index: number; 49 | }[]; 50 | __getPage(increment: number): Promise<{ 51 | address: string; 52 | index: number; 53 | }[]>; 54 | getAccounts(): Promise; 55 | getAccountsAndIndexAndDType: () => Promise 56 | getIndexByAddress(address: string): number; 57 | private _addressFromIndex; 58 | } 59 | export { }; 60 | 61 | -------------------------------------------------------------------------------- /src/background/service/keyringclass/index.ts: -------------------------------------------------------------------------------- 1 | // "use strict"; 2 | // var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | // if (k2 === undefined) k2 = k; 4 | // var desc = Object.getOwnPropertyDescriptor(m, k); 5 | // if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | // desc = { enumerable: true, get: function() { return m[k]; } }; 7 | // } 8 | // Object.defineProperty(o, k2, desc); 9 | // }) : (function(o, m, k, k2) { 10 | // if (k2 === undefined) k2 = k; 11 | // o[k2] = m[k]; 12 | // })); 13 | // var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | // for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | // }; 16 | // Object.defineProperty(exports, "__esModule", { value: true }); 17 | // __exportStar(require("./hd-keyring"), exports); 18 | // __exportStar(require("./simple-keyring"), exports); 19 | 20 | import { HdKeyring } from './hd-keyring'; 21 | import { SimpleKeyring } from './simple-keyring'; 22 | 23 | export default { HdKeyring, SimpleKeyring }; 24 | -------------------------------------------------------------------------------- /src/background/service/session.ts: -------------------------------------------------------------------------------- 1 | import { permissionService } from '@/background/service'; 2 | 3 | export class Session { 4 | origin = ''; 5 | 6 | icon = ''; 7 | 8 | name = ''; 9 | 10 | constructor(data) { 11 | if (data) { 12 | this.setProp(data); 13 | } 14 | } 15 | 16 | setProp({ origin, icon, name }) { 17 | this.origin = origin; 18 | this.icon = icon; 19 | this.name = name; 20 | } 21 | } 22 | 23 | // for each tab 24 | const sessionMap = new Map(); 25 | 26 | const getSession = (id) => { 27 | return sessionMap.get(id); 28 | }; 29 | 30 | const getOrCreateSession = (id) => { 31 | if (sessionMap.has(id)) { 32 | return getSession(id); 33 | } 34 | 35 | return createSession(id, null); 36 | }; 37 | 38 | const createSession = (id, data) => { 39 | const session = new Session(data); 40 | sessionMap.set(id, session); 41 | 42 | return session; 43 | }; 44 | 45 | const deleteSession = (id) => { 46 | sessionMap.delete(id); 47 | }; 48 | 49 | const broadcastEvent = (ev, data?, origin?) => { 50 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 51 | let sessions: any[] = []; 52 | sessionMap.forEach((session, key) => { 53 | if (permissionService.hasPermission(session.origin)) { 54 | sessions.push({ 55 | key, 56 | ...session 57 | }); 58 | } 59 | }); 60 | 61 | // same origin 62 | if (origin) { 63 | sessions = sessions.filter((session) => session.origin === origin); 64 | } 65 | 66 | sessions.forEach((session) => { 67 | try { 68 | session.pushMessage?.(ev, data); 69 | } catch (e) { 70 | if (sessionMap.has(session.key)) { 71 | deleteSession(session.key); 72 | } 73 | } 74 | }); 75 | }; 76 | 77 | export default { 78 | getSession, 79 | getOrCreateSession, 80 | deleteSession, 81 | broadcastEvent 82 | }; 83 | -------------------------------------------------------------------------------- /src/background/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as createPersistStore } from './persisitStore'; 2 | export { default as PromiseFlow } from './promiseFlow'; 3 | 4 | export const underline2Camelcase = (str: string) => { 5 | return str.replace(/_(.)/g, (m, p1) => p1.toUpperCase()); 6 | }; 7 | 8 | export const wait = (fn: () => void, ms = 1000) => { 9 | return new Promise((resolve) => { 10 | setTimeout(() => { 11 | fn(); 12 | resolve(true); 13 | }, ms); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/background/utils/onekey/bip340.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@noble/hashes/sha256'; 2 | 3 | import { Buffer } from 'buffer'; 4 | import ecc from './nobleSecp256k1Wrapper'; 5 | const TAGGED_HASH_PREFIXES = { 6 | TapLeaf: Buffer.from([ 7 | 174, 234, 143, 220, 66, 8, 152, 49, 5, 115, 75, 88, 8, 29, 30, 38, 56, 211, 95, 28, 181, 64, 8, 212, 211, 87, 202, 8 | 3, 190, 120, 233, 238, 174, 234, 143, 220, 66, 8, 152, 49, 5, 115, 75, 88, 8, 29, 30, 38, 56, 211, 95, 28, 181, 64, 9 | 8, 212, 211, 87, 202, 3, 190, 120, 233, 238 10 | ]), 11 | TapBranch: Buffer.from([ 12 | 25, 65, 161, 242, 229, 110, 185, 95, 162, 169, 241, 148, 190, 92, 1, 247, 33, 111, 51, 237, 130, 176, 145, 70, 52, 13 | 144, 208, 91, 245, 22, 160, 21, 25, 65, 161, 242, 229, 110, 185, 95, 162, 169, 241, 148, 190, 92, 1, 247, 33, 111, 14 | 51, 237, 130, 176, 145, 70, 52, 144, 208, 91, 245, 22, 160, 21 15 | ]), 16 | TapSighash: Buffer.from([ 17 | 244, 10, 72, 223, 75, 42, 112, 200, 180, 146, 75, 242, 101, 70, 97, 237, 61, 149, 253, 102, 163, 19, 235, 135, 35, 18 | 117, 151, 198, 40, 228, 160, 49, 244, 10, 72, 223, 75, 42, 112, 200, 180, 146, 75, 242, 101, 70, 97, 237, 61, 149, 19 | 253, 102, 163, 19, 235, 135, 35, 117, 151, 198, 40, 228, 160, 49 20 | ]), 21 | TapTweak: Buffer.from([ 22 | 232, 15, 225, 99, 156, 156, 160, 80, 227, 175, 27, 57, 193, 67, 198, 62, 66, 156, 188, 235, 21, 217, 64, 251, 181, 23 | 197, 161, 244, 175, 87, 197, 233, 232, 15, 225, 99, 156, 156, 160, 80, 227, 175, 27, 57, 193, 67, 198, 62, 66, 156, 24 | 188, 235, 21, 217, 64, 251, 181, 197, 161, 244, 175, 87, 197, 233 25 | ]) 26 | }; 27 | 28 | function taggedHash(prefix: string, data: Buffer): Buffer { 29 | const tagged: Buffer = TAGGED_HASH_PREFIXES[prefix]; 30 | 31 | const hash = sha256.create(); 32 | hash.update(tagged); 33 | hash.update(data); 34 | return Buffer.from(hash.digest()); 35 | } 36 | 37 | export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 38 | return taggedHash('TapTweak', Buffer.concat(h ? [pubKey, h] : [pubKey])); 39 | } 40 | 41 | export function tweakPublicKey( 42 | pubKey: Buffer, 43 | h: Buffer | undefined = undefined 44 | ): { parity: number; x: Uint8Array } | null { 45 | if (!Buffer.isBuffer(pubKey)) return null; 46 | if (pubKey.length !== 32) return null; 47 | if (h && h.length !== 32) return null; 48 | 49 | const tweakHash = tapTweakHash(pubKey, h); 50 | const res: { parity: number; xOnlyPubkey: Uint8Array } = ecc.xOnlyPointAddTweak(pubKey, tweakHash); 51 | if (!res || res.xOnlyPubkey === null) return null; 52 | 53 | return { 54 | parity: res.parity, 55 | x: Buffer.from(res.xOnlyPubkey) 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/background/utils/persisitStore.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | import { debounce } from 'debounce'; 3 | 4 | import { storage } from '@/background/webapi'; 5 | 6 | const persistStorage = (name: string, obj: object) => { 7 | debounce(storage.set(name, obj), 1000); 8 | }; 9 | 10 | interface CreatePersistStoreParams { 11 | name: string; 12 | template?: T; 13 | fromStorage?: boolean; 14 | } 15 | 16 | const createPersistStore = async ({ 17 | name, 18 | template = Object.create(null), 19 | fromStorage = true 20 | }: CreatePersistStoreParams): Promise => { 21 | let tpl = template; 22 | 23 | if (fromStorage) { 24 | const storageCache = await storage.get(name); 25 | tpl = storageCache || template; 26 | if (!storageCache) { 27 | await storage.set(name, tpl); 28 | } 29 | } 30 | 31 | const createProxy = (obj: A): A => 32 | new Proxy(obj, { 33 | set(target, prop, value) { 34 | target[prop] = value; 35 | 36 | persistStorage(name, target); 37 | 38 | return true; 39 | }, 40 | 41 | deleteProperty(target, prop) { 42 | if (Reflect.has(target, prop)) { 43 | Reflect.deleteProperty(target, prop); 44 | 45 | persistStorage(name, target); 46 | } 47 | 48 | return true; 49 | } 50 | }); 51 | return createProxy(tpl); 52 | }; 53 | 54 | export default createPersistStore; 55 | -------------------------------------------------------------------------------- /src/background/utils/promiseFlow.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import compose from 'koa-compose'; 4 | 5 | export default class PromiseFlow { 6 | private _tasks: ((args: any) => void)[] = []; 7 | _context: any = {}; 8 | requestedApproval = false; 9 | 10 | use(fn): PromiseFlow { 11 | if (typeof fn !== 'function') { 12 | throw new Error('promise need function to handle'); 13 | } 14 | this._tasks.push(fn); 15 | 16 | return this; 17 | } 18 | 19 | callback() { 20 | return compose(this._tasks); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/background/webapi/index.ts: -------------------------------------------------------------------------------- 1 | export { default as notification } from './notification'; 2 | export { default as storage } from './storage'; 3 | export { default as winMgr } from './window'; 4 | export { default as tab } from './tab'; 5 | -------------------------------------------------------------------------------- /src/background/webapi/notification.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable no-unused-vars */ 3 | const create = (url: string | undefined, title: string, message: string, priority = 0) => { 4 | const randomId = +new Date(); 5 | }; 6 | 7 | export default { create }; 8 | -------------------------------------------------------------------------------- /src/background/webapi/storage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { browserStorageLocalGet, browserStorageLocalSet } from './browser'; 3 | 4 | let cacheMap; 5 | 6 | const get = async (prop?) => { 7 | if (cacheMap) { 8 | return cacheMap.get(prop); 9 | } 10 | 11 | const result = await browserStorageLocalGet(null); 12 | cacheMap = new Map(Object.entries(result).map(([k, v]) => [k, v])); 13 | 14 | return prop ? result[prop] : result; 15 | }; 16 | 17 | const set = async (prop, value): Promise => { 18 | await browserStorageLocalSet({ [prop]: value }); 19 | cacheMap.set(prop, value); 20 | }; 21 | 22 | const byteInUse = async (): Promise => { 23 | return new Promise((resolve, reject) => { 24 | if (chrome) { 25 | chrome.storage.local.getBytesInUse((value) => { 26 | resolve(value); 27 | }); 28 | } else { 29 | reject('ByteInUse only works in Chrome'); 30 | } 31 | }); 32 | }; 33 | 34 | export default { 35 | get, 36 | set, 37 | byteInUse 38 | }; 39 | -------------------------------------------------------------------------------- /src/background/webapi/tab.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { EventEmitter } from 'events'; 3 | 4 | import { browserTabsCreate, browserTabsOnRemoved, browserTabsOnUpdated } from './browser'; 5 | 6 | const tabEvent = new EventEmitter(); 7 | 8 | browserTabsOnUpdated((tabId, changeInfo) => { 9 | if (changeInfo.url) { 10 | tabEvent.emit('tabUrlChanged', tabId, changeInfo.url); 11 | } 12 | }); 13 | 14 | // window close will trigger this event also 15 | browserTabsOnRemoved((tabId) => { 16 | tabEvent.emit('tabRemove', tabId); 17 | }); 18 | 19 | const createTab = async (url): Promise => { 20 | const tab = await browserTabsCreate({ 21 | active: true, 22 | url 23 | }); 24 | 25 | return tab?.id; 26 | }; 27 | 28 | const openIndexPage = (route = ''): Promise => { 29 | const url = `index.html${route && `#${route}`}`; 30 | 31 | return createTab(url); 32 | }; 33 | 34 | const queryCurrentActiveTab = async function () { 35 | return new Promise((resolve) => { 36 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { 37 | if (!tabs) return resolve({}); 38 | const [activeTab] = tabs; 39 | const { id, title, url } = activeTab; 40 | const { origin, protocol } = url ? new URL(url) : { origin: null, protocol: null }; 41 | 42 | if (!origin || origin === 'null') { 43 | resolve({}); 44 | return; 45 | } 46 | 47 | resolve({ id, title, origin, protocol, url }); 48 | }); 49 | }); 50 | }; 51 | 52 | export default tabEvent; 53 | 54 | export { createTab, openIndexPage, queryCurrentActiveTab }; 55 | -------------------------------------------------------------------------------- /src/background/webapi/window.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import { EventEmitter } from 'events'; 4 | 5 | import { IS_WINDOWS } from '@/shared/constant'; 6 | 7 | import { 8 | browserWindowsCreate, 9 | browserWindowsGetCurrent, 10 | browserWindowsOnFocusChanged, 11 | browserWindowsOnRemoved, 12 | browserWindowsRemove, 13 | browserWindowsUpdate 14 | } from './browser'; 15 | 16 | const event = new EventEmitter(); 17 | 18 | browserWindowsOnFocusChanged((winId) => { 19 | event.emit('windowFocusChange', winId); 20 | }); 21 | 22 | browserWindowsOnRemoved((winId) => { 23 | event.emit('windowRemoved', winId); 24 | }); 25 | 26 | const BROWSER_HEADER = 80; 27 | const WINDOW_SIZE = { 28 | width: 400 + (IS_WINDOWS ? 14 : 0), // idk why windows cut the width. 29 | height: 600 30 | }; 31 | 32 | const create = async ({ url, ...rest }): Promise => { 33 | const { 34 | top: cTop, 35 | left: cLeft, 36 | width 37 | } = await browserWindowsGetCurrent({ 38 | windowTypes: ['normal'] 39 | } as any); 40 | 41 | const top = cTop! + BROWSER_HEADER; 42 | const left = cLeft! + width! - WINDOW_SIZE.width; 43 | 44 | const currentWindow = await browserWindowsGetCurrent(); 45 | let win; 46 | if (currentWindow.state === 'fullscreen') { 47 | // browser.windows.create not pass state to chrome 48 | win = await browserWindowsCreate({ 49 | focused: true, 50 | url, 51 | type: 'popup', 52 | ...rest, 53 | width: undefined, 54 | height: undefined, 55 | left: undefined, 56 | top: undefined, 57 | state: 'fullscreen' 58 | }); 59 | } else { 60 | win = await browserWindowsCreate({ 61 | focused: true, 62 | url, 63 | type: 'popup', 64 | top, 65 | left, 66 | ...WINDOW_SIZE, 67 | ...rest 68 | }); 69 | } 70 | 71 | // shim firefox 72 | if (win.left !== left) { 73 | await browserWindowsUpdate(win.id!, { left, top }); 74 | } 75 | 76 | return win.id; 77 | }; 78 | 79 | const remove = async (winId) => { 80 | return browserWindowsRemove(winId); 81 | }; 82 | 83 | const openNotification = ({ route = '', ...rest } = {}): Promise => { 84 | const url = `notification.html${route && `#${route}`}`; 85 | 86 | return create({ url, ...rest }); 87 | }; 88 | 89 | export default { 90 | openNotification, 91 | event, 92 | remove 93 | }; 94 | -------------------------------------------------------------------------------- /src/content-script/pageProvider/dedupePromise.ts: -------------------------------------------------------------------------------- 1 | import { ethErrors } from 'eth-rpc-errors'; 2 | 3 | class DedupePromise { 4 | private _blackList: string[]; 5 | private _tasks: Record = {}; 6 | 7 | constructor(blackList) { 8 | this._blackList = blackList; 9 | } 10 | 11 | async call(key: string, defer: () => Promise) { 12 | if (this._blackList.includes(key) && this._tasks[key]) { 13 | throw ethErrors.rpc.transactionRejected('there is a pending request, please request after it resolved'); 14 | } 15 | 16 | return new Promise((resolve) => { 17 | this._tasks[key] = (this._tasks[key] || 0) + 1; 18 | 19 | resolve( 20 | defer().finally(() => { 21 | this._tasks[key]--; 22 | if (!this._tasks[key]) { 23 | delete this._tasks[key]; 24 | } 25 | }) 26 | ); 27 | }); 28 | } 29 | } 30 | 31 | export default DedupePromise; 32 | -------------------------------------------------------------------------------- /src/content-script/pageProvider/pushEventHandlers.ts: -------------------------------------------------------------------------------- 1 | import { ethErrors } from 'eth-rpc-errors'; 2 | 3 | import { KaswareProvider } from './index'; 4 | 5 | class PushEventHandlers { 6 | provider: KaswareProvider; 7 | 8 | constructor(provider) { 9 | this.provider = provider; 10 | } 11 | 12 | _emit(event, data) { 13 | if (this.provider._initialized) { 14 | this.provider.emit(event, data); 15 | } 16 | } 17 | 18 | connect = (data) => { 19 | if (!this.provider._isConnected) { 20 | this.provider._isConnected = true; 21 | this.provider._state.isConnected = true; 22 | this._emit('connect', data); 23 | } 24 | }; 25 | 26 | unlock = () => { 27 | this.provider._isUnlocked = true; 28 | this.provider._state.isUnlocked = true; 29 | }; 30 | 31 | lock = () => { 32 | this.provider._isUnlocked = false; 33 | }; 34 | 35 | disconnect = () => { 36 | this.provider._isConnected = false; 37 | this.provider._state.isConnected = false; 38 | this.provider._state.accounts = null; 39 | this.provider._selectedAddress = null; 40 | const disconnectError = ethErrors.provider.disconnected(); 41 | 42 | this._emit('accountsChanged', []); 43 | this._emit('disconnect', disconnectError); 44 | this._emit('close', disconnectError); 45 | }; 46 | 47 | accountsChanged = (accounts: string[]) => { 48 | if (accounts?.[0] === this.provider._selectedAddress) { 49 | return; 50 | } 51 | 52 | this.provider._selectedAddress = accounts?.[0]; 53 | this.provider._state.accounts = accounts; 54 | this._emit('accountsChanged', accounts); 55 | }; 56 | 57 | networkChanged = ({ network }) => { 58 | this.connect({}); 59 | 60 | if (network !== this.provider._network) { 61 | this.provider._network = network; 62 | this._emit('networkChanged', network); 63 | } 64 | }; 65 | } 66 | 67 | export default PushEventHandlers; 68 | -------------------------------------------------------------------------------- /src/content-script/pageProvider/readyPromise.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | /* eslint-disable no-unused-vars */ 4 | class ReadyPromise { 5 | private _allCheck: boolean[] = []; 6 | private _tasks: { 7 | resolve(value: unknown): void; 8 | fn(): Promise; 9 | }[] = []; 10 | 11 | constructor(count) { 12 | this._allCheck = [...Array(count)]; 13 | } 14 | 15 | check = (index) => { 16 | this._allCheck[index - 1] = true; 17 | this._proceed(); 18 | }; 19 | 20 | uncheck = (index) => { 21 | this._allCheck[index - 1] = false; 22 | }; 23 | 24 | private _proceed = () => { 25 | if (this._allCheck.some((_) => !_)) { 26 | return; 27 | } 28 | 29 | while (this._tasks.length) { 30 | const { resolve, fn } = this._tasks.shift()!; 31 | resolve(fn()); 32 | } 33 | }; 34 | 35 | call = (fn) => { 36 | return new Promise((resolve) => { 37 | this._tasks.push({ 38 | fn, 39 | resolve 40 | }); 41 | 42 | this._proceed(); 43 | }); 44 | }; 45 | } 46 | 47 | export default ReadyPromise; 48 | -------------------------------------------------------------------------------- /src/content-script/pageProvider/utils.ts: -------------------------------------------------------------------------------- 1 | let tryCount = 0; 2 | const checkLoaded = (callback) => { 3 | tryCount++; 4 | if (tryCount > 600) { 5 | // some error happen? 6 | return; 7 | } 8 | if (document.readyState === 'complete') { 9 | callback(); 10 | return true; 11 | } else { 12 | setTimeout(() => { 13 | checkLoaded(callback); 14 | }, 100); 15 | } 16 | }; 17 | const domReadyCall = (callback) => { 18 | checkLoaded(callback); 19 | 20 | // if (document.readyState === 'complete') { 21 | // callback(); 22 | // } else { 23 | // const domContentLoadedHandler = (e) => { 24 | // callback(); 25 | // document.removeEventListener('DOMContentLoaded', domContentLoadedHandler); 26 | // }; 27 | // document.addEventListener('DOMContentLoaded', domContentLoadedHandler); 28 | // } 29 | }; 30 | 31 | const $ = document.querySelector.bind(document); 32 | 33 | export { domReadyCall, $ }; 34 | -------------------------------------------------------------------------------- /src/shared/eventBus.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | type Listener = (params?: any) => void; 4 | 5 | class EventBus { 6 | events: Record = {}; 7 | 8 | emit = (type: string, params?: any) => { 9 | const listeners = this.events[type]; 10 | if (listeners) { 11 | listeners.forEach((fn) => { 12 | fn(params); 13 | }); 14 | } 15 | }; 16 | 17 | once = (type: string, fn: Listener) => { 18 | const listeners = this.events[type]; 19 | const func = (...params: any[]) => { 20 | fn(...params); 21 | this.events[type] = this.events[type].filter((item) => item !== func); 22 | }; 23 | if (listeners) { 24 | this.events[type].push(func); 25 | } else { 26 | this.events[type] = [func]; 27 | } 28 | }; 29 | 30 | addEventListener = (type: string, fn: Listener) => { 31 | const listeners = this.events[type]; 32 | if (listeners) { 33 | this.events[type].push(fn); 34 | } else { 35 | this.events[type] = [fn]; 36 | } 37 | }; 38 | 39 | removeEventListener = (type: string, fn: Listener) => { 40 | const listeners = this.events[type]; 41 | if (listeners) { 42 | this.events[type] = this.events[type].filter((item) => item !== fn); 43 | } 44 | }; 45 | 46 | removeAllEventListeners = (type: string) => { 47 | this.events[type] = []; 48 | }; 49 | } 50 | 51 | export default new EventBus(); 52 | -------------------------------------------------------------------------------- /src/shared/lib/satsname-utils.ts: -------------------------------------------------------------------------------- 1 | export function getSatsName(nameStr: string): { suffix: string; name: string } | false { 2 | const name = nameStr.toLowerCase().trim().split(/\s/g)[0]; 3 | 4 | const arr = name.split('.'); 5 | const len = arr.length === 2; 6 | if (!len) { 7 | return false; 8 | } 9 | const suffix = arr[1]; 10 | return { 11 | suffix, 12 | name 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/shared/modules.ts: -------------------------------------------------------------------------------- 1 | declare module 'browser-passworder' { 2 | export function encrypt(password: string, privateKey: any): Promise; 3 | export function decrypt(password: string, encrypted: string): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /src/shared/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 2 | /// 3 | /// 4 | /// 5 | 6 | declare namespace NodeJS { 7 | interface ProcessEnv { 8 | readonly NODE_ENV: 'development' | 'production' | 'test'; 9 | readonly PUBLIC_URL: string; 10 | } 11 | } 12 | 13 | declare module '*.avif' { 14 | const src: string; 15 | export default src; 16 | } 17 | 18 | declare module '*.bmp' { 19 | const src: string; 20 | export default src; 21 | } 22 | 23 | declare module '*.gif' { 24 | const src: string; 25 | export default src; 26 | } 27 | 28 | declare module '*.jpg' { 29 | const src: string; 30 | export default src; 31 | } 32 | 33 | declare module '*.jpeg' { 34 | const src: string; 35 | export default src; 36 | } 37 | 38 | declare module '*.png' { 39 | const src: string; 40 | export default src; 41 | } 42 | 43 | declare module '*.webp' { 44 | const src: string; 45 | export default src; 46 | } 47 | 48 | declare module '*.svg' { 49 | import * as React from 'react'; 50 | 51 | export const ReactComponent: React.FunctionComponent & { title?: string }>; 52 | 53 | const src: string; 54 | export default src; 55 | } 56 | 57 | declare module '*.module.css' { 58 | const classes: { readonly [key: string]: string }; 59 | export default classes; 60 | } 61 | 62 | declare module '*.module.scss' { 63 | const classes: { readonly [key: string]: string }; 64 | export default classes; 65 | } 66 | 67 | declare module '*.module.sass' { 68 | const classes: { readonly [key: string]: string }; 69 | export default classes; 70 | } 71 | 72 | declare module '*.module.less' { 73 | const classes: { readonly [key: string]: string }; 74 | export default classes; 75 | } 76 | -------------------------------------------------------------------------------- /src/shared/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { keyBy } from 'lodash'; 2 | 3 | import browser from '@/background/webapi/browser'; 4 | import { AddressFlagType, CHAINS } from '@/shared/constant'; 5 | 6 | import { Address, UtxoEntryReference } from 'kaspa-wasm'; 7 | import { IKaspaUTXOWithoutBigint } from '../types'; 8 | import BroadcastChannelMessage from './message/broadcastChannelMessage'; 9 | import PortMessage from './message/portMessage'; 10 | 11 | const Message = { 12 | BroadcastChannelMessage, 13 | PortMessage 14 | }; 15 | 16 | declare global { 17 | const langLocales: Record>; 18 | } 19 | 20 | const t = (name) => browser.i18n.getMessage(name); 21 | 22 | const format = (str, ...args) => { 23 | return args.reduce((m, n) => m.replace('_s_', n), str); 24 | }; 25 | 26 | export { Message, format, t }; 27 | 28 | const chainsDict = keyBy(CHAINS, 'serverId'); 29 | export const getChain = (chainId?: string) => { 30 | if (!chainId) { 31 | return null; 32 | } 33 | return chainsDict[chainId]; 34 | }; 35 | 36 | // Check if address flag is enabled 37 | export const checkAddressFlag = (currentFlag: number, flag: AddressFlagType): boolean => { 38 | return Boolean(currentFlag & flag); 39 | }; 40 | 41 | export const getKaspaUTXOWithoutBigint = (utxos: UtxoEntryReference[]) => { 42 | const newUtxos: IKaspaUTXOWithoutBigint[] = utxos.map((v) => { 43 | return { 44 | amount: v.amount.toString(), 45 | blockDaaScore: v.blockDaaScore.toString(), 46 | entry: { 47 | address: v.entry?.address?.toString(), 48 | amount: v.entry.amount.toString(), 49 | blockDaaScore: v.entry.blockDaaScore.toString(), 50 | isCoinbase: v.entry.isCoinbase, 51 | outpoint: v.entry.outpoint, 52 | scriptPublicKey: v.entry.scriptPublicKey 53 | }, 54 | isCoinbase: v.isCoinbase 55 | } as IKaspaUTXOWithoutBigint; 56 | }); 57 | return newUtxos; 58 | }; 59 | export const getKaspaUTXOWithBigint = (utxos: IKaspaUTXOWithoutBigint[]) => { 60 | const newUtxos: UtxoEntryReference[] = utxos.map((v) => { 61 | if (v.entry.address) { 62 | return { 63 | amount: BigInt(v.amount), 64 | blockDaaScore: BigInt(v.blockDaaScore), 65 | entry: { 66 | address: new Address(v.entry.address), 67 | amount: BigInt(v.entry.amount), 68 | blockDaaScore: BigInt(v.entry.blockDaaScore.toString()), 69 | isCoinbase: v.entry.isCoinbase, 70 | outpoint: v.entry.outpoint, 71 | scriptPublicKey: v.entry.scriptPublicKey 72 | }, 73 | isCoinbase: v.isCoinbase 74 | }; 75 | } else { 76 | return { 77 | amount: BigInt(v.amount), 78 | blockDaaScore: BigInt(v.blockDaaScore), 79 | entry: { 80 | // address: new Address(v.entry.address), 81 | amount: BigInt(v.entry.amount), 82 | blockDaaScore: BigInt(v.entry.blockDaaScore.toString()), 83 | isCoinbase: v.entry.isCoinbase, 84 | outpoint: v.entry.outpoint, 85 | scriptPublicKey: v.entry.scriptPublicKey 86 | }, 87 | isCoinbase: v.isCoinbase 88 | }; 89 | } 90 | }); 91 | return newUtxos; 92 | }; 93 | -------------------------------------------------------------------------------- /src/shared/utils/message/broadcastChannelMessage.ts: -------------------------------------------------------------------------------- 1 | import Message from './index'; 2 | 3 | export default class BroadcastChannelMessage extends Message { 4 | private _channel: BroadcastChannel; 5 | 6 | constructor(name?: string) { 7 | super(); 8 | if (!name) { 9 | throw new Error('the broadcastChannel name is missing'); 10 | } 11 | 12 | this._channel = new BroadcastChannel(name); 13 | } 14 | 15 | connect = () => { 16 | this._channel.onmessage = ({ data: { type, data } }) => { 17 | if (type === 'message') { 18 | this.emit('message', data); 19 | } else if (type === 'response') { 20 | this.onResponse(data); 21 | } 22 | }; 23 | 24 | return this; 25 | }; 26 | 27 | listen = (listenCallback) => { 28 | this.listenCallback = listenCallback; 29 | 30 | this._channel.onmessage = ({ data: { type, data } }) => { 31 | if (type === 'request') { 32 | this.onRequest(data); 33 | } 34 | }; 35 | 36 | return this; 37 | }; 38 | 39 | send = (type, data) => { 40 | this._channel.postMessage({ 41 | type, 42 | data 43 | }); 44 | }; 45 | 46 | dispose = () => { 47 | this._dispose(); 48 | this._channel.close(); 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/shared/utils/message/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | /* eslint-disable no-unused-vars */ 3 | /* eslint-disable @typescript-eslint/no-explicit-any */ 4 | /** 5 | * this script is live in content-script / dapp's page 6 | */ 7 | import { ethErrors } from 'eth-rpc-errors'; 8 | import { EventEmitter } from 'events'; 9 | 10 | abstract class Message extends EventEmitter { 11 | // available id list 12 | // max concurrent request limit 13 | private _requestIdPool = [...Array(500).keys()]; 14 | protected _EVENT_PRE = 'KASWARE_WALLET_'; 15 | protected listenCallback: any; 16 | 17 | private _waitingMap = new Map< 18 | number, 19 | { 20 | data: any; 21 | resolve: (arg: any) => any; 22 | reject: (arg: any) => any; 23 | } 24 | >(); 25 | 26 | abstract send(type: string, data: any): void; 27 | 28 | request = (data) => { 29 | if (!this._requestIdPool.length) { 30 | throw ethErrors.rpc.limitExceeded(); 31 | } 32 | const ident = this._requestIdPool.shift()!; 33 | 34 | return new Promise((resolve, reject) => { 35 | this._waitingMap.set(ident, { 36 | data, 37 | resolve, 38 | reject 39 | }); 40 | 41 | this.send('request', { ident, data }); 42 | }); 43 | }; 44 | 45 | onResponse = async ({ ident, res, err }: any = {}) => { 46 | // the url may update 47 | if (!this._waitingMap.has(ident)) { 48 | return; 49 | } 50 | 51 | const { resolve, reject } = this._waitingMap.get(ident)!; 52 | 53 | this._requestIdPool.push(ident); 54 | this._waitingMap.delete(ident); 55 | err ? reject(err) : resolve(res); 56 | }; 57 | 58 | onRequest = async ({ ident, data }) => { 59 | if (this.listenCallback) { 60 | let res, err; 61 | 62 | try { 63 | res = await this.listenCallback(data); 64 | } catch (e: any) { 65 | err = { 66 | message: e.message, 67 | stack: e.stack 68 | }; 69 | e.code && (err.code = e.code); 70 | e.data && (err.data = e.data); 71 | } 72 | 73 | this.send('response', { ident, res, err }); 74 | } 75 | }; 76 | 77 | _dispose = () => { 78 | for (const request of this._waitingMap.values()) { 79 | request.reject(ethErrors.provider.userRejectedRequest()); 80 | } 81 | 82 | this._waitingMap.clear(); 83 | }; 84 | } 85 | 86 | export default Message; 87 | -------------------------------------------------------------------------------- /src/shared/utils/message/portMessage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { browserRuntimeConnect } from '@/background/webapi/browser'; 3 | 4 | import Message from './index'; 5 | 6 | class PortMessage extends Message { 7 | port: any | null = null; 8 | listenCallback: any; 9 | 10 | constructor(port?: any) { 11 | super(); 12 | 13 | if (port) { 14 | this.port = port; 15 | } 16 | } 17 | 18 | connect = (name?: string) => { 19 | this.port = browserRuntimeConnect(undefined, name ? { name } : undefined); 20 | this.port.onMessage.addListener(({ _type_, data }) => { 21 | if (_type_ === `${this._EVENT_PRE}message`) { 22 | this.emit('message', data); 23 | return; 24 | } 25 | 26 | if (_type_ === `${this._EVENT_PRE}response`) { 27 | this.onResponse(data); 28 | } 29 | }); 30 | 31 | return this; 32 | }; 33 | 34 | listen = (listenCallback: any) => { 35 | if (!this.port) return; 36 | this.listenCallback = listenCallback; 37 | this.port.onMessage.addListener(({ _type_, data }) => { 38 | if (_type_ === `${this._EVENT_PRE}request`) { 39 | this.onRequest(data); 40 | } 41 | }); 42 | 43 | return this; 44 | }; 45 | 46 | send = (type, data) => { 47 | if (!this.port) return; 48 | try { 49 | this.port.postMessage({ _type_: `${this._EVENT_PRE}${type}`, data }); 50 | } catch (e) { 51 | // DO NOTHING BUT CATCH THIS ERROR 52 | } 53 | }; 54 | 55 | dispose = () => { 56 | this._dispose(); 57 | this.port?.disconnect(); 58 | }; 59 | } 60 | 61 | export default PortMessage; 62 | -------------------------------------------------------------------------------- /src/ui/components/AccountSelect/index.less: -------------------------------------------------------------------------------- 1 | .account-select-container { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | gap: 0.625rem; 6 | justify-content: space-between; 7 | 8 | height: 40px; 9 | 10 | background: #2a2626; 11 | border: none; 12 | border-radius: 0.3rem; 13 | 14 | cursor: pointer; 15 | 16 | &:hover { 17 | background: #383535; 18 | border: none; 19 | border-radius: 0.3rem; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ui/components/AccountSelect/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { useNavigate } from '@/ui/pages/MainRoute'; 4 | import { useCurrentAccount } from '@/ui/state/accounts/hooks'; 5 | import { shortAddress } from '@/ui/utils'; 6 | import { RightOutlined } from '@ant-design/icons'; 7 | 8 | import { Icon } from '../Icon'; 9 | import { Row } from '../Row'; 10 | import { Text } from '../Text'; 11 | import './index.less'; 12 | 13 | const AccountSelect = () => { 14 | const navigate = useNavigate(); 15 | const currentAccount = useCurrentAccount(); 16 | 17 | return ( 18 | { 26 | navigate('SwitchAccountScreen'); 27 | }}> 28 | 29 | 30 | {/* */} 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default AccountSelect; 37 | -------------------------------------------------------------------------------- /src/ui/components/ActionComponent/Loading/index.less: -------------------------------------------------------------------------------- 1 | .loading-container { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | z-index: 10; 11 | } 12 | -------------------------------------------------------------------------------- /src/ui/components/ActionComponent/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'react'; 2 | 3 | import { colors } from '@/ui/theme/colors'; 4 | import { fontSizes } from '@/ui/theme/font'; 5 | import { spacing } from '@/ui/theme/spacing'; 6 | import { LoadingOutlined } from '@ant-design/icons'; 7 | 8 | import { Text } from '../../Text'; 9 | import './index.less'; 10 | 11 | export interface LoadingProps { 12 | text?: string; 13 | onClose?: () => void; 14 | } 15 | 16 | const $baseViewStyle: CSSProperties = { 17 | width: '100vw', 18 | height: '100vh', 19 | backgroundColor: 'rgba(0,0,0,0.8)', 20 | display: 'flex', 21 | alignItems: 'center', 22 | justifyContent: 'center', 23 | flexDirection: 'column', 24 | gap: spacing.small 25 | }; 26 | 27 | export function Loading(props: LoadingProps) { 28 | const { text } = props; 29 | return ( 30 |
31 |
32 | 38 | {text && } 39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/ui/components/ActionComponent/Tip/index.less: -------------------------------------------------------------------------------- 1 | .tip-container { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | z-index: 10; 11 | } 12 | -------------------------------------------------------------------------------- /src/ui/components/ActionComponent/Tip/index.tsx: -------------------------------------------------------------------------------- 1 | import { Column } from '../../Column'; 2 | import { Popover } from '../../Popover'; 3 | import { Text } from '../../Text'; 4 | import './index.less'; 5 | 6 | export interface TipProps { 7 | text?: string; 8 | onClose?: () => void; 9 | } 10 | 11 | export function Tip(props: TipProps) { 12 | const { text, onClose } = props; 13 | return ( 14 | { 16 | onClose && onClose(); 17 | }}> 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/ui/components/ActionComponent/Toast/index.less: -------------------------------------------------------------------------------- 1 | .action-container { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | z-index: 10; 11 | pointer-events: none; 12 | } 13 | 14 | .toast { 15 | opacity: 0; 16 | margin-bottom: 50px; 17 | animation: fade, raise; 18 | animation-duration: 2s, 0.5s; 19 | 20 | --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 21 | --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); 22 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 23 | } 24 | 25 | @keyframes fade { 26 | 0% { 27 | opacity: 0; 28 | } 29 | 15% { 30 | opacity: 1; 31 | } 32 | 75% { 33 | opacity: 1; 34 | } 35 | 100% { 36 | opacity: 0; 37 | } 38 | } 39 | 40 | @keyframes raise { 41 | 0% { 42 | margin-bottom: 20px; 43 | } 44 | 100% { 45 | margin-bottom: 50px; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ui/components/ActionComponent/Toast/index.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, useEffect } from 'react'; 2 | 3 | import { colors } from '@/ui/theme/colors'; 4 | 5 | import { Text } from '../../Text'; 6 | import './index.less'; 7 | 8 | export type ToastPresets = keyof typeof $viewPresets; 9 | export interface ToastProps { 10 | preset: ToastPresets; 11 | content: string; 12 | onClose: () => void; 13 | } 14 | 15 | const $baseViewStyle = { 16 | alignSelf: 'end', 17 | padding: 4, 18 | borderRadius: 4, 19 | paddingLeft: 8, 20 | paddingRight: 8, 21 | marginLeft: 16, 22 | marginRight: 16 23 | } as CSSProperties; 24 | 25 | const $viewPresets = { 26 | info: Object.assign({}, $baseViewStyle, { 27 | backgroundColor: colors.black_dark 28 | }) as CSSProperties, 29 | 30 | success: Object.assign({}, $baseViewStyle, { 31 | backgroundColor: colors.green 32 | }) as CSSProperties, 33 | 34 | error: Object.assign({}, $baseViewStyle, { 35 | backgroundColor: colors.danger 36 | }) as CSSProperties, 37 | 38 | warning: Object.assign({}, $baseViewStyle, { 39 | backgroundColor: colors.warning 40 | }) as CSSProperties 41 | }; 42 | 43 | export function Toast(props: ToastProps) { 44 | const { preset, content, onClose } = props; 45 | useEffect(() => { 46 | setTimeout(() => { 47 | onClose(); 48 | }, 5000); 49 | }, []); 50 | 51 | return ( 52 |
53 |
54 | 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/ui/components/AddressBar/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { useAccountAddress } from '@/ui/state/accounts/hooks'; 4 | import { copyToClipboard, shortAddress } from '@/ui/utils'; 5 | 6 | import { CopyOutlined } from '@ant-design/icons'; 7 | import { useTranslation } from 'react-i18next'; 8 | import { useTools } from '../ActionComponent'; 9 | import { Row } from '../Row'; 10 | import { Text } from '../Text'; 11 | 12 | export function AddressBar() { 13 | const { t } = useTranslation(); 14 | const tools = useTools(); 15 | const address = useAccountAddress(); 16 | return ( 17 | { 21 | copyToClipboard(address).then(() => { 22 | tools.toastSuccess(t('Copied')); 23 | }); 24 | }}> 25 | 26 | {/**/} 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/ui/components/AddressDetailPopover/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { useBlockstreamUrl } from '@/ui/state/settings/hooks'; 4 | import { copyToClipboard, shortAddress } from '@/ui/utils'; 5 | 6 | import { useTranslation } from 'react-i18next'; 7 | import { useTools } from '../ActionComponent'; 8 | import { Card } from '../Card'; 9 | import { Column } from '../Column'; 10 | import { Icon } from '../Icon'; 11 | import { Popover } from '../Popover'; 12 | import { Row } from '../Row'; 13 | import { Text } from '../Text'; 14 | 15 | export const AddressDetailPopover = ({ address, onClose }: { address: string; onClose: () => void }) => { 16 | const { t } = useTranslation(); 17 | const tools = useTools(); 18 | const blockstreamUrl = useBlockstreamUrl(); 19 | return ( 20 | 21 | 22 | 23 | { 26 | copyToClipboard(address).then(() => { 27 | tools.toastSuccess(t('Copied')); 28 | }); 29 | }}> 30 | 31 | 37 | 38 | 39 | 40 | 41 | { 44 | window.open(`${blockstreamUrl}/address/${address}`); 45 | }}> 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/ui/components/AddressText/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from 'react'; 2 | 3 | import { ToAddressInfo } from '@/shared/types'; 4 | import { ColorTypes } from '@/ui/theme/colors'; 5 | import { shortAddress } from '@/ui/utils'; 6 | 7 | import { AddressDetailPopover } from '../AddressDetailPopover'; 8 | import { Column } from '../Column'; 9 | import { Text } from '../Text'; 10 | 11 | export const AddressText = (props: { 12 | address?: string; 13 | addressInfo?: ToAddressInfo; 14 | textCenter?: boolean; 15 | color?: ColorTypes; 16 | }) => { 17 | const [popoverVisible, setPopoverVisible] = useState(false); 18 | const address = useMemo(() => { 19 | if (props.address) { 20 | return props.address; 21 | } 22 | if (props.addressInfo) { 23 | return props.addressInfo.address; 24 | } 25 | return ''; 26 | }, []); 27 | return ( 28 | 29 | { 31 | setPopoverVisible(true); 32 | }}> 33 | 34 | 35 | {popoverVisible && ( 36 | { 39 | setPopoverVisible(false); 40 | }} 41 | /> 42 | )} 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/ui/components/AddressTypeCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactEventHandler } from 'react'; 2 | 3 | import { AddressAssets } from '@/shared/types'; 4 | import { fontSizes } from '@/ui/theme/font'; 5 | 6 | import { sompiToKAS } from '@/ui/utils'; 7 | import { Card } from '../Card'; 8 | import { Column } from '../Column'; 9 | import { CopyableAddress } from '../CopyableAddress'; 10 | import { Icon } from '../Icon'; 11 | import { Row } from '../Row'; 12 | import { Text } from '../Text'; 13 | 14 | interface AddressTypeCardProps { 15 | label: string; 16 | address: string; 17 | checked: boolean; 18 | assets: AddressAssets; 19 | onClick?: ReactEventHandler; 20 | } 21 | export function AddressTypeCard(props: AddressTypeCardProps) { 22 | const { onClick, label, address, checked, assets } = props; 23 | const hasVault = Boolean(assets.sompi && assets.sompi > 0); 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {checked && } 35 | 36 | {hasVault && ( 37 | 38 | 39 | 40 | 41 | 42 | 43 | )} 44 | 45 | 46 | ); 47 | } 48 | 49 | interface AddressTypeCardProp2 { 50 | label: string; 51 | items: { 52 | address: string; 53 | path: string; 54 | sompi: number; 55 | }[]; 56 | checked: boolean; 57 | onClick?: ReactEventHandler; 58 | } 59 | 60 | export function AddressTypeCard2(props: AddressTypeCardProp2) { 61 | const { onClick, label, items, checked } = props; 62 | return ( 63 | 64 | 65 | 66 | 67 | 68 | 69 | {checked && } 70 | 71 | 72 | {items.map((v) => ( 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {v.sompi > 0 && ( 81 | 82 | 83 | 84 | 85 | )} 86 | 87 | ))} 88 | 89 | 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/ui/components/BaseView/index.less: -------------------------------------------------------------------------------- 1 | .base-view :hover { 2 | opacity: 0.8; 3 | } 4 | -------------------------------------------------------------------------------- /src/ui/components/Card/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | 3 | import { colors } from '@/ui/theme/colors'; 4 | import { spacingGap } from '@/ui/theme/spacing'; 5 | 6 | import { BaseView, BaseViewProps } from '../BaseView'; 7 | 8 | export interface CardProps extends BaseViewProps { 9 | preset?: Presets; 10 | } 11 | 12 | const $baseViewStyle = { 13 | display: 'flex', 14 | flexDirection: 'row', 15 | gap: spacingGap.md, 16 | backgroundColor: colors.black_dark, 17 | alignItems: 'center', 18 | justifyContent: 'center', 19 | borderRadius: 5 20 | } as CSSProperties; 21 | 22 | const $viewPresets = { 23 | auto: Object.assign({}, $baseViewStyle, { 24 | paddingTop: spacingGap.lg, 25 | paddingBottom: spacingGap.lg, 26 | paddingLeft: spacingGap.lg, 27 | paddingRight: spacingGap.lg, 28 | minHeight: 50 29 | } as CSSProperties) as CSSProperties, 30 | style1: Object.assign({}, $baseViewStyle, { 31 | height: '75px', 32 | paddingTop: spacingGap.sm, 33 | paddingBottom: spacingGap.sm, 34 | paddingLeft: spacingGap.lg, 35 | paddingRight: spacingGap.lg 36 | }) as CSSProperties, 37 | style2: Object.assign({}, $baseViewStyle, { 38 | paddingTop: spacingGap.sm, 39 | paddingBottom: spacingGap.sm, 40 | paddingLeft: spacingGap.lg, 41 | paddingRight: spacingGap.lg 42 | }) as CSSProperties, 43 | style3: Object.assign({}, $baseViewStyle, { 44 | paddingTop: spacingGap.xs, 45 | paddingBottom: spacingGap.xs, 46 | paddingLeft: spacingGap.sm, 47 | paddingRight: spacingGap.sm 48 | }) as CSSProperties 49 | }; 50 | 51 | type Presets = keyof typeof $viewPresets; 52 | 53 | export function Card(props: CardProps) { 54 | const { style: $styleOverride, preset, ...rest } = props; 55 | const $style = Object.assign({}, $viewPresets[preset || 'auto'], $styleOverride); 56 | return ; 57 | } 58 | -------------------------------------------------------------------------------- /src/ui/components/Column/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | 3 | import { spacingGap } from '@/ui/theme/spacing'; 4 | 5 | import { BaseView, BaseViewProps } from '../BaseView'; 6 | 7 | export type ColumnProps = BaseViewProps; 8 | const $columnStyle = { 9 | display: 'flex', 10 | flexDirection: 'column', 11 | justifyContent: 'flex-start', 12 | gap: spacingGap.md 13 | } as CSSProperties; 14 | 15 | export function Column(props: ColumnProps) { 16 | const { style: $styleOverride, ...rest } = props; 17 | const $style = Object.assign({}, $columnStyle, $styleOverride); 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/ui/components/Content/index.less: -------------------------------------------------------------------------------- 1 | // For scrollbar 2 | .content::-webkit-scrollbar { 3 | width: 4px; 4 | height: 2px; 5 | } 6 | 7 | .content::-webkit-scrollbar-thumb { 8 | border-radius: 3px; 9 | box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.1); 10 | background: rgba(255, 255, 255, 0.854); 11 | } 12 | 13 | .content ::-webkit-scrollbar-track { 14 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1) inset; 15 | } 16 | 17 | .content::-webkit-scrollbar-corner { 18 | background: transparent; 19 | } 20 | -------------------------------------------------------------------------------- /src/ui/components/Content/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | 3 | import { spacing, spacingGap } from '@/ui/theme/spacing'; 4 | 5 | import { BaseView, BaseViewProps } from '../BaseView'; 6 | import './index.less'; 7 | 8 | type Presets = keyof typeof $viewPresets; 9 | export interface ContentProps extends BaseViewProps { 10 | preset?: Presets; 11 | } 12 | const $contentStyle = { 13 | backgroundColor: '#1C1919', 14 | display: 'flex', 15 | flex: 1, 16 | flexDirection: 'column', 17 | justifyItems: 'center', 18 | gap: spacingGap.lg, 19 | 20 | alignSelf: 'stretch', 21 | overflowY: 'auto', 22 | overflowX: 'hidden' 23 | } as CSSProperties; 24 | 25 | const $viewPresets = { 26 | large: Object.assign({}, $contentStyle, { 27 | alignItems: 'stretch', 28 | padding: spacing.large, 29 | paddingTop: 0 30 | }), 31 | middle: Object.assign({}, $contentStyle, { 32 | alignItems: 'center', 33 | justifyContent: 'center', 34 | width: 285, 35 | alignSelf: 'center' 36 | } as CSSProperties) 37 | }; 38 | 39 | export function Content(props: ContentProps) { 40 | const { style: $styleOverride, preset, ...rest } = props; 41 | 42 | const $style = Object.assign({}, $viewPresets[preset || 'large'], $styleOverride); 43 | return ; 44 | } 45 | -------------------------------------------------------------------------------- /src/ui/components/CopyableAddress/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { copyToClipboard, shortAddress } from '@/ui/utils'; 4 | import { useTools } from '../ActionComponent'; 5 | import { Icon } from '../Icon'; 6 | import { Row } from '../Row'; 7 | import { Text } from '../Text'; 8 | import { useTranslation } from 'react-i18next'; 9 | 10 | export function CopyableAddress({ address }: { address: string }) { 11 | const { t } = useTranslation(); 12 | const tools = useTools(); 13 | return ( 14 | { 18 | copyToClipboard(address).then(() => { 19 | tools.toastSuccess(t('Copied')); 20 | }); 21 | }}> 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/ui/components/Empty/index.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Text } from '../Text'; 3 | 4 | interface EmptyProps { 5 | text?: string; 6 | } 7 | export function Empty(props: EmptyProps) { 8 | const { text } = props; 9 | const { t } = useTranslation(); 10 | const content = text || t('NO DATA'); 11 | return ( 12 |
16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/ui/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | 3 | import { BaseView, BaseViewProps } from '../BaseView'; 4 | 5 | export type FooterProps = BaseViewProps; 6 | 7 | const $footerBaseStyle = { 8 | display: 'block', 9 | minHeight: 20, 10 | padding: 10, 11 | paddingBottom: 20, 12 | bottom: 0 13 | } as CSSProperties; 14 | 15 | export function Footer(props: FooterProps) { 16 | const { style: $styleOverride, ...rest } = props; 17 | const $style = Object.assign({}, $footerBaseStyle, $styleOverride); 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/ui/components/FooterButtonContainer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Column } from '../Column'; 4 | import { Footer } from '../Footer'; 5 | import { Row } from '../Row'; 6 | 7 | export function FooterButtonContainer({ children }: { children: React.ReactNode }) { 8 | return ( 9 | 10 | 11 |
12 | {children} 13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/components/Grid/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, ReactEventHandler } from 'react'; 2 | 3 | import { Gap, spacingGap } from '@/ui/theme/spacing'; 4 | 5 | export interface GridProps { 6 | style?: CSSProperties; 7 | children: React.ReactNode; 8 | gap?: Gap; 9 | onClick?: ReactEventHandler; 10 | columns?: number; 11 | } 12 | 13 | const $gridStyle = { 14 | display: 'grid', 15 | flexDirection: 'column', 16 | justifyContent: 'flex-start', 17 | gap: spacingGap.md 18 | } as CSSProperties; 19 | 20 | export function Grid(props: GridProps) { 21 | const { children, style: $styleOverride, gap, columns, onClick } = props; 22 | const $style = Object.assign( 23 | {}, 24 | $gridStyle, 25 | $styleOverride, 26 | gap ? { gap: spacingGap[gap] } : {}, 27 | columns ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : {}, 28 | onClick ? { cursor: 'pointer' } : {} 29 | ); 30 | return
{children}
; 31 | } 32 | -------------------------------------------------------------------------------- /src/ui/components/Header/index.module.less: -------------------------------------------------------------------------------- 1 | .connected { 2 | @apply flex items-center justify-center border-0 border-custom-green bg-custom-green-rgba text-custom-green p-2_5 h-8_5 text-lg rounded border-opacity-20 leading-5_5; 3 | } 4 | -------------------------------------------------------------------------------- /src/ui/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import React, { useMemo } from 'react'; 4 | 5 | import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 7 | 8 | import { useTranslation } from 'react-i18next'; 9 | import { Column } from '../Column'; 10 | import { Icon } from '../Icon'; 11 | import { Logo } from '../Logo'; 12 | import { Row } from '../Row'; 13 | import { Text } from '../Text'; 14 | import './index.module.less'; 15 | 16 | interface HeaderProps { 17 | onBack?: () => void; 18 | title?: string; 19 | LeftComponent?: React.ReactNode; 20 | RightComponent?: React.ReactNode; 21 | children?: React.ReactNode; 22 | } 23 | 24 | export function Header(props: HeaderProps) { 25 | const { t } = useTranslation(); 26 | const { onBack, title, LeftComponent, RightComponent, children } = props; 27 | 28 | const CenterComponent = useMemo(() => { 29 | if (children) { 30 | return children; 31 | } else if (title) { 32 | return ; 33 | } else { 34 | return ; 35 | } 36 | }, [title]); 37 | return ( 38 |
39 | 46 | 47 | 48 | {LeftComponent} 49 | {onBack && ( 50 | { 52 | onBack(); 53 | }}> 54 | 55 | 56 | 57 | 58 | {/* */} 59 | 60 | )} 61 | 62 | 63 | 64 | {CenterComponent} 65 | 66 | 67 | {RightComponent} 68 | 69 | 70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/ui/components/Iframe.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { CSSProperties, memo, useMemo } from 'react'; 3 | 4 | export type IframeProps = { preview: string; style?: CSSProperties; ref: any }; 5 | 6 | const Iframe = ({ preview, style, ref }: IframeProps) => { 7 | return useMemo( 8 | () => ( 9 | 17 | ), 18 | [preview] 19 | ); 20 | }; 21 | 22 | export default memo(Iframe, (p, n) => { 23 | return p.preview === n.preview; 24 | }); 25 | -------------------------------------------------------------------------------- /src/ui/components/Image/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import React, { CSSProperties } from 'react'; 4 | 5 | import { fontSizes } from '@/ui/theme/font'; 6 | 7 | interface ImageProps { 8 | src?: string; 9 | size?: number | string; 10 | style?: CSSProperties; 11 | containerStyle?: CSSProperties; 12 | onClick?: React.MouseEventHandler; 13 | } 14 | 15 | export function Image(props: ImageProps) { 16 | const { src, size, style: $imageStyleOverride, onClick } = props; 17 | 18 | return ( 19 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/ui/components/Input/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/src/ui/components/Input/index.less -------------------------------------------------------------------------------- /src/ui/components/Layout/index.less: -------------------------------------------------------------------------------- 1 | // For scrollbar 2 | .layout::-webkit-scrollbar { 3 | width: 4px; 4 | height: 2px; 5 | } 6 | 7 | .layout::-webkit-scrollbar-thumb { 8 | border-radius: 3px; 9 | box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.1); 10 | background: rgba(255, 255, 255, 0.854); 11 | } 12 | 13 | .layout ::-webkit-scrollbar-track { 14 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1) inset; 15 | } 16 | 17 | .layout::-webkit-scrollbar-corner { 18 | background: transparent; 19 | } 20 | -------------------------------------------------------------------------------- /src/ui/components/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | 3 | import './index.less'; 4 | 5 | export interface LayoutProps { 6 | children?: React.ReactNode; 7 | style?: CSSProperties; 8 | } 9 | export function Layout(props: LayoutProps) { 10 | const { children, style: $styleBase } = props; 11 | return ( 12 |
25 | {children} 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/ui/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import { fontSizes } from '@/ui/theme/font'; 2 | 3 | import { Image } from '../Image'; 4 | import { Row } from '../Row'; 5 | import { Text } from '../Text'; 6 | 7 | export function Logo(props: { preset?: 'large' | 'small' }) { 8 | const { preset } = props; 9 | if (preset === 'large') { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } else { 18 | return ( 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/components/NavTabBar/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { useNavigate } from '@/ui/pages/MainRoute'; 4 | import { useReadTab, useUnreadAppSummary } from '@/ui/state/accounts/hooks'; 5 | import { TabOption } from '@/ui/state/global/reducer'; 6 | import { colors } from '@/ui/theme/colors'; 7 | 8 | import { BaseView } from '../BaseView'; 9 | import { Column } from '../Column'; 10 | import { Grid } from '../Grid'; 11 | import { Icon, IconTypes } from '../Icon'; 12 | 13 | export function NavTabBar({ tab }: { tab: TabOption }) { 14 | return ( 15 | 16 | 17 | {/* 18 | */} 19 | 20 | 21 | ); 22 | } 23 | 24 | function TabButton({ tabName, icon, isActive }: { tabName: TabOption; icon: IconTypes; isActive: boolean }) { 25 | const navigate = useNavigate(); 26 | const unreadApp = useUnreadAppSummary(); 27 | const readTab = useReadTab(); 28 | return ( 29 | { 33 | if (tabName === 'home') { 34 | navigate('MainScreen'); 35 | } else if (tabName === 'mint') { 36 | navigate('DiscoverTabScreen'); 37 | } else if (tabName === 'app') { 38 | navigate('AppTabScrren'); 39 | readTab('app'); 40 | } else if (tabName === 'settings') { 41 | navigate('SettingsTabScreen'); 42 | } 43 | }}> 44 | 45 | 46 | {tabName === 'app' && unreadApp && ( 47 | 57 | )} 58 | 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/ui/components/NoticePopover/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import { Checkbox } from 'antd'; 4 | import { useEffect, useState } from 'react'; 5 | 6 | import { colors } from '@/ui/theme/colors'; 7 | 8 | import { Button } from '../Button'; 9 | import { Column } from '../Column'; 10 | import { Icon } from '../Icon'; 11 | import { Popover } from '../Popover'; 12 | import { Row } from '../Row'; 13 | import { Text } from '../Text'; 14 | 15 | export const NoticePopover = ({ onClose }: { onClose: () => void }) => { 16 | const [checked1, setChecked1] = useState(false); 17 | const [checked2, setChecked2] = useState(false); 18 | 19 | const [enable, setEnable] = useState(false); 20 | const [coolDown, setCoolDown] = useState(3); 21 | 22 | useEffect(() => { 23 | if (coolDown > 0) { 24 | setTimeout(() => { 25 | setCoolDown(coolDown - 1); 26 | }, 1000); 27 | } else { 28 | setEnable(true); 29 | } 30 | }, [coolDown]); 31 | 32 | return ( 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | { 44 | setChecked1(e.target.checked); 45 | }}> 46 | 47 |
48 | 49 |
50 | 51 | 52 |