├── .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 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/arrow-up-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/check.svg:
--------------------------------------------------------------------------------
1 |
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 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/compass-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/copy-solid.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/discord.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/down.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/eye-slash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/gear-solid.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/github.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/grid-solid.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/info.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/kaspa-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
26 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/kaspa.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/list-solid.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/ordinals.svg:
--------------------------------------------------------------------------------
1 |
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 |
12 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/telegram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/user-solid.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/wallet-solid.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/build/_raw/images/icons/warning.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
65 |
66 |
67 | );
68 | };
69 |
--------------------------------------------------------------------------------
/src/ui/components/OutputValueBar/index.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties, useEffect, useState } from 'react';
2 |
3 | import { colors } from '@/ui/theme/colors';
4 |
5 | import { Column } from '../Column';
6 | import { Input } from '../Input';
7 | import { Row } from '../Row';
8 | import { Text } from '../Text';
9 |
10 | enum FeeRateType {
11 | CURRENT,
12 | CUSTOM
13 | }
14 |
15 | export function OutputValueBar({ defaultValue, onChange }: { defaultValue: number; onChange: (val: number) => void }) {
16 | const options = [
17 | {
18 | title: 'Current',
19 | value: defaultValue
20 | },
21 | {
22 | title: 'Custom'
23 | }
24 | ];
25 | const [optionIndex, setOptionIndex] = useState(FeeRateType.CURRENT);
26 | const [inputVal, setInputVal] = useState('');
27 |
28 | useEffect(() => {
29 | let val: any = defaultValue;
30 | if (optionIndex === FeeRateType.CUSTOM) {
31 | val = parseInt(inputVal);
32 | } else if (options.length > 0) {
33 | val = options[optionIndex].value;
34 | }
35 | onChange(val);
36 | }, [optionIndex, inputVal]);
37 |
38 | return (
39 |
40 |
41 | {options.map((v, index) => {
42 | const selected = index === optionIndex;
43 | return (
44 | {
47 | setOptionIndex(index);
48 | }}
49 | style={Object.assign(
50 | {},
51 | {
52 | borderWidth: 1,
53 | borderColor: 'rgba(255,255,255,0.3)',
54 | height: 75,
55 | width: 120,
56 | textAlign: 'center',
57 | padding: 4,
58 | borderRadius: 5,
59 | display: 'flex',
60 | flexDirection: 'column',
61 | justifyContent: 'center',
62 | cursor: 'pointer'
63 | } as CSSProperties,
64 | selected ? { backgroundColor: colors.primary } : {}
65 | )}>
66 |
67 | {v.value && }
68 |
69 | );
70 | })}
71 |
72 | {optionIndex === FeeRateType.CUSTOM && (
73 | {
80 | setInputVal(val);
81 | }}
82 | onBlur={() => {
83 | if (inputVal) {
84 | const val = parseInt(inputVal || '0') + '';
85 | setInputVal(val);
86 | }
87 | }}
88 | autoFocus={true}
89 | />
90 | )}
91 |
92 | );
93 | }
94 |
--------------------------------------------------------------------------------
/src/ui/components/Popover/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { CloseOutlined } from '@ant-design/icons';
4 |
5 | import { Row } from '../Row';
6 |
7 | export const Popover = ({ children, onClose }: { children: React.ReactNode; onClose?: () => void }) => {
8 | return (
9 |
14 |
15 | {
16 | onClose && {
20 | onClose();
21 | }}>
22 |
23 |
24 | }
25 |
26 | {children}
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/ui/components/RefreshButton/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactEventHandler, useState } from 'react';
2 |
3 | import { ReloadOutlined } from '@ant-design/icons';
4 |
5 | import { Row } from '../Row';
6 | import { Text } from '../Text';
7 |
8 | export function RefreshButton({ onClick }: { onClick: ReactEventHandler }) {
9 | const [leftTime, setLeftTime] = useState(0);
10 | const [disabled, setDisabled] = useState(false);
11 | const wait = (seconds: number) => {
12 | if (seconds > 0) {
13 | setLeftTime(seconds);
14 | setTimeout(() => {
15 | wait(seconds - 1);
16 | }, 1000);
17 | return;
18 | }
19 | setDisabled(false);
20 | };
21 |
22 | return (
23 | {
27 | if (disabled) {
28 | return;
29 | }
30 | setDisabled(true);
31 | wait(5);
32 | onClick(e);
33 | }}>
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/ui/components/RemoveContactPopover/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { useMemo } from 'react';
4 |
5 | import { shortAddress, useWallet } from '@/ui/utils';
6 | import { faTrashCan } from '@fortawesome/free-solid-svg-icons';
7 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8 |
9 | import { ContactBookItem } from '@/background/service/contactBook';
10 | import { Button } from '../Button';
11 | import { Card } from '../Card';
12 | import { Column } from '../Column';
13 | import { Popover } from '../Popover';
14 | import { Row } from '../Row';
15 | import { Text } from '../Text';
16 |
17 | export const RemoveContactPopover = ({ keyring, onClose }: { keyring: ContactBookItem; onClose: () => void }) => {
18 | const wallet = useWallet();
19 | const displayAddress = useMemo(() => {
20 | if (!keyring.address) {
21 | return 'Invalid';
22 | }
23 | const address = keyring.address;
24 | return shortAddress(address);
25 | }, []);
26 | return (
27 |
28 |
29 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
69 |
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/src/ui/components/Responsive.tsx:
--------------------------------------------------------------------------------
1 | import { useExtensionIsInTab } from '../features/browser/tabs';
2 |
3 | export const AppDimensions = (props) => {
4 | const extensionIsInTab = useExtensionIsInTab();
5 |
6 | return (
7 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/ui/components/Row/index.less:
--------------------------------------------------------------------------------
1 | // For scrollbar
2 | .row-container::-webkit-scrollbar {
3 | width: 2px;
4 | height: 8px;
5 | }
6 |
7 | .row-container::-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 | .row-container::-webkit-scrollbar-track {
14 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1) inset;
15 | }
16 |
17 | .row-container::-webkit-scrollbar-corner {
18 | background: transparent;
19 | }
20 |
--------------------------------------------------------------------------------
/src/ui/components/Row/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from 'react';
2 |
3 | import { spacingGap } from '@/ui/theme/spacing';
4 |
5 | import { BaseView, BaseViewProps } from '../BaseView';
6 | import './index.less';
7 |
8 | export type RowProps = BaseViewProps;
9 |
10 | const $rowStyle = {
11 | display: 'flex',
12 | flexDirection: 'row',
13 | gap: spacingGap.md
14 | } as CSSProperties;
15 |
16 | export function Row(props: RowProps) {
17 | const { style: $styleOverride, ...rest } = props;
18 | const $style = Object.assign({}, $rowStyle, $styleOverride);
19 | return ;
20 | }
21 |
--------------------------------------------------------------------------------
/src/ui/components/TabBar/index.less:
--------------------------------------------------------------------------------
1 | .selected-tab {
2 | border-bottom: 2px solid #f6ae2d;
3 | }
4 |
--------------------------------------------------------------------------------
/src/ui/components/TextArea/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from 'react';
2 |
3 | import { colors } from '@/ui/theme/colors';
4 | import { fontSizes } from '@/ui/theme/font';
5 |
6 | import { BaseView, BaseViewProps } from '../BaseView';
7 |
8 | export interface TextAreaProps extends BaseViewProps {
9 | text: string;
10 | }
11 |
12 | const $textAreaStyle = {
13 | backgroundColor: colors.bg2,
14 | flexWrap: 'wrap',
15 | padding: 10,
16 | userSelect: 'text',
17 | maxHeight: 384,
18 | overflow: 'auto',
19 | whiteSpace: 'pre-wrap',
20 | wordBreak: 'break-word',
21 | fontSize: fontSizes.xs
22 | } as CSSProperties;
23 |
24 | export function TextArea(props: TextAreaProps) {
25 | const { style: $styleOverride, text, ...rest } = props;
26 | const $style = Object.assign({}, $textAreaStyle, $styleOverride);
27 | return (
28 |
29 | {text}
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/src/ui/components/UpgradePopover/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { useEffect, useState } from 'react';
4 |
5 | import { VersionDetail } from '@/shared/types';
6 | import { useVersionInfo } from '@/ui/state/settings/hooks';
7 | import { fontSizes } from '@/ui/theme/font';
8 | import { useWallet } from '@/ui/utils';
9 |
10 | import { Button } from '../Button';
11 | import { Column } from '../Column';
12 | import { Popover } from '../Popover';
13 | import { Row } from '../Row';
14 | import { Text } from '../Text';
15 |
16 | export const UpgradePopover = ({ onClose }: { onClose: () => void }) => {
17 | const versionInfo = useVersionInfo();
18 |
19 | const [versionDetail, setVersionDetail] = useState({ version: '', changelogs: [], title: '' });
20 | const wallet = useWallet();
21 | useEffect(() => {
22 | if (!versionInfo.newVersion) return;
23 | wallet
24 | .getVersionDetail(versionInfo.newVersion)
25 | .then((res) => {
26 | setVersionDetail(res);
27 | })
28 | .catch((e) => {
29 | console.log(e);
30 | });
31 | }, [versionInfo.newVersion]);
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {versionDetail.changelogs.map((str, index) => (
41 |
42 | {str}
43 |
44 | ))}
45 |
46 |
47 |
48 | {
52 | if (onClose) {
53 | onClose();
54 | }
55 | }}
56 | />
57 |
58 | {
63 | window.open('https://kasware.xyz/extension/update');
64 | }}
65 | />
66 |
67 |
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/src/ui/components/WarningPopover/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { ColorTypes, colors } from '@/ui/theme/colors';
4 |
5 | import { Button } from '../Button';
6 | import { Card } from '../Card';
7 | import { Column } from '../Column';
8 | import { Popover } from '../Popover';
9 | import { Row } from '../Row';
10 | import { Text } from '../Text';
11 |
12 | const riskColor: { [key: string]: ColorTypes } = {
13 | high: 'danger',
14 | low: 'orange'
15 | };
16 |
17 | export const WarningPopover = ({
18 | risks,
19 | onClose
20 | }: {
21 | risks: { level: 'high' | 'low'; color?: ColorTypes; desc: string }[];
22 | onClose: () => void;
23 | }) => {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 | {risks.map((risk, index) => (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | ))}
42 |
43 |
44 |
45 | {
50 | if (onClose) {
51 | onClose();
52 | }
53 | }}
54 | />
55 |
56 |
57 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/src/ui/components/WebsiteBar/index.tsx:
--------------------------------------------------------------------------------
1 | import { fontSizes } from '@/ui/theme/font';
2 |
3 | import { Card } from '../Card';
4 | import { Image } from '../Image';
5 | import { Row } from '../Row';
6 | import { Text } from '../Text';
7 |
8 | const WebsiteBar = ({ session }: { session: { origin: string; icon: string; name: string } }) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default WebsiteBar;
20 |
--------------------------------------------------------------------------------
/src/ui/components/index.tsx:
--------------------------------------------------------------------------------
1 | import { AddressBar } from './AddressBar';
2 | import { Button } from './Button';
3 | import { Card } from './Card';
4 | import { Column } from './Column';
5 | import { Content } from './Content';
6 | import { Footer } from './Footer';
7 | import { Grid } from './Grid';
8 | import { Header } from './Header';
9 | import { Icon } from './Icon';
10 | import { Image } from './Image';
11 | import { Input } from './Input';
12 | import { Layout } from './Layout';
13 | import { Logo } from './Logo';
14 | import { Row } from './Row';
15 | import { Text } from './Text';
16 | import { TextArea } from './TextArea';
17 |
18 | export {
19 | Button,
20 | Layout,
21 | Content,
22 | Icon,
23 | Header,
24 | Text,
25 | Logo,
26 | Input,
27 | Footer,
28 | AddressBar,
29 | Column,
30 | Row,
31 | Card,
32 | Grid,
33 | Image,
34 | TextArea
35 | };
36 |
--------------------------------------------------------------------------------
/src/ui/features/browser/tabs.ts:
--------------------------------------------------------------------------------
1 | import { isNumber } from 'lodash-es';
2 | import { useCallback, useEffect, useState } from 'react';
3 |
4 | import browser, {
5 | browserTabsCreate,
6 | browserTabsGetCurrent,
7 | browserTabsQuery,
8 | browserTabsUpdate
9 | } from '@/background/webapi/browser';
10 |
11 | export const openExtensionInTab = async () => {
12 | const url = browser.runtime.getURL('index.html');
13 | const tab = await browserTabsCreate({ url });
14 | return tab;
15 | };
16 |
17 | export const extensionIsInTab = async () => {
18 | return Boolean(await browserTabsGetCurrent());
19 | };
20 |
21 | export const focusExtensionTab = async () => {
22 | const tab = await browserTabsGetCurrent();
23 | if (tab && isNumber(tab?.id) && tab?.id !== browser.tabs.TAB_ID_NONE) {
24 | browserTabsUpdate(tab.id, { active: true });
25 | }
26 | };
27 |
28 | export const useExtensionIsInTab = () => {
29 | const [isInTab, setIsInTab] = useState(false);
30 | useEffect(() => {
31 | const init = async () => {
32 | const inTab = await extensionIsInTab();
33 | setIsInTab(inTab);
34 | };
35 | init();
36 | }, []);
37 | return isInTab;
38 | };
39 |
40 | export const useOpenExtensionInTab = () => {
41 | return useCallback(async () => {
42 | await openExtensionInTab();
43 | window.close();
44 | }, []);
45 | };
46 |
47 | export const getCurrentTab = async () => {
48 | const tabs = await browserTabsQuery({ active: true, currentWindow: true });
49 | return tabs[0];
50 | };
51 |
--------------------------------------------------------------------------------
/src/ui/pages/Account/AddKeyringScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { Card, Column, Content, Header, Icon, Layout, Row, Text } from '@/ui/components';
4 |
5 | import { ImportOutlined, KeyOutlined, PlusCircleOutlined } from '@ant-design/icons';
6 | import { useTranslation } from 'react-i18next';
7 | import { useNavigate } from '../MainRoute';
8 |
9 | export default function AddKeyringScreen() {
10 | const { t } = useTranslation();
11 | const navigate = useNavigate();
12 | return (
13 |
14 | {
16 | window.history.go(-1);
17 | }}
18 | title={t('Create a new wallet')}
19 | />
20 |
21 |
22 | {
27 | navigate('CreateHDWalletScreen', { isImport: false });
28 | }}>
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {
46 | navigate('CreateHDWalletScreen', { isImport: true });
47 | }}>
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {
65 | navigate('CreateSimpleWalletScreen');
66 | }}>
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | );
83 | }
84 |
--------------------------------------------------------------------------------
/src/ui/pages/Account/CreateAccountScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import React, { useEffect, useState } from 'react';
4 |
5 | import { Button, Column, Content, Header, Input, Layout } from '@/ui/components';
6 | import { useTools } from '@/ui/components/ActionComponent';
7 | import { useSetCurrentAccountCallback } from '@/ui/state/accounts/hooks';
8 | import { useCurrentKeyring } from '@/ui/state/keyrings/hooks';
9 | import { useWallet } from '@/ui/utils';
10 |
11 | import { useTranslation } from 'react-i18next';
12 | import { useNavigate } from '../MainRoute';
13 |
14 | export default function CreateAccountScreen() {
15 | const { t } = useTranslation();
16 | const navigate = useNavigate();
17 | const wallet = useWallet();
18 | const tools = useTools();
19 | const setCurrentAccount = useSetCurrentAccountCallback();
20 | const currentKeyring = useCurrentKeyring();
21 | const [alianName, setAlianName] = useState('');
22 | const [defaultName, setDefaultName] = useState('');
23 | const handleOnClick = async () => {
24 | await wallet.deriveNewAccountFromMnemonic(currentKeyring, alianName || defaultName);
25 | tools.toastSuccess('Success');
26 | const currentAccount = await wallet.getCurrentAccount();
27 | setCurrentAccount(currentAccount);
28 | navigate('MainScreen');
29 | };
30 |
31 | const handleOnKeyUp = (e: React.KeyboardEvent) => {
32 | if ('Enter' == e.key) {
33 | handleOnClick();
34 | }
35 | };
36 |
37 | const init = async () => {
38 | const accountName = await wallet.getNextAlianName(currentKeyring);
39 | setDefaultName(accountName);
40 | };
41 | useEffect(() => {
42 | init();
43 | }, []);
44 |
45 | return (
46 |
47 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/src/ui/pages/Account/UnlockScreen/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import { Column, Content, Layout, Row } from '@/ui/components';
4 | import { useTools } from '@/ui/components/ActionComponent';
5 | import { Button } from '@/ui/components/Button';
6 | import { Input } from '@/ui/components/Input';
7 | import { Logo } from '@/ui/components/Logo';
8 | import { Text } from '@/ui/components/Text';
9 | import { useUnlockCallback } from '@/ui/state/global/hooks';
10 | import { getUiType, useWallet } from '@/ui/utils';
11 |
12 | import { t } from 'i18next';
13 | import { useNavigate } from '../../MainRoute';
14 |
15 | export default function UnlockScreen() {
16 | const wallet = useWallet();
17 | const navigate = useNavigate();
18 | const [password, setPassword] = useState('');
19 | const [disabled, setDisabled] = useState(true);
20 | const UIType = getUiType();
21 | const isInNotification = UIType.isNotification;
22 | const unlock = useUnlockCallback();
23 | const tools = useTools();
24 | const btnClick = async () => {
25 | // run(password);
26 | try {
27 | await unlock(password);
28 | if (!isInNotification) {
29 | const hasVault = await wallet.hasVault();
30 | if (!hasVault) {
31 | navigate('WelcomeScreen');
32 | return;
33 | } else {
34 | navigate('MainScreen');
35 | return;
36 | }
37 | }
38 | } catch (e) {
39 | tools.toastError('PASSWORD ERROR');
40 | }
41 | };
42 |
43 | const handleOnKeyUp = (e: React.KeyboardEvent) => {
44 | if (!disabled && 'Enter' == e.key) {
45 | btnClick();
46 | }
47 | };
48 |
49 | useEffect(() => {
50 | if (password) {
51 | setDisabled(false);
52 | } else {
53 | setDisabled(true);
54 | }
55 | }, [password]);
56 | return (
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {/* */}
66 | setPassword(e.target.value)}
70 | onKeyUp={(e) => handleOnKeyUp(e)}
71 | autoFocus={true}
72 | />
73 |
74 |
75 |
76 |
77 |
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/src/ui/pages/Approval/ApprovalScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | /* eslint-disable no-unused-vars */
3 | /* eslint-disable @typescript-eslint/no-unused-vars */
4 | import { useEffect, useState } from 'react';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | import { useApproval, useWallet } from '@/ui/utils';
8 |
9 | import * as ApprovalComponent from './components';
10 |
11 | export default function ApprovalScreen() {
12 | const wallet = useWallet();
13 | const [getApproval, resolveApproval, rejectApproval] = useApproval();
14 |
15 | const [approval, setApproval] = useState(null);
16 |
17 | const navigate = useNavigate();
18 |
19 | const init = async () => {
20 | const approval = await getApproval();
21 | if (!approval) {
22 | navigate('/');
23 | return null;
24 | }
25 | setApproval(approval);
26 | if (approval.origin || approval.params.origin) {
27 | document.title = approval.origin || approval.params.origin;
28 | }
29 | const account = await wallet.getCurrentAccount();
30 | if (!account) {
31 | rejectApproval();
32 | return;
33 | }
34 | };
35 |
36 | useEffect(() => {
37 | init();
38 | }, []);
39 |
40 | if (!approval) return <>>;
41 | const { approvalComponent, params, origin, requestDefer } = approval;
42 | const CurrentApprovalComponent = ApprovalComponent[approvalComponent];
43 | return ;
44 | }
45 |
--------------------------------------------------------------------------------
/src/ui/pages/Approval/ConnectedSitesScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { useEffect, useState } from 'react';
4 |
5 | import { ConnectedSite } from '@/background/service/permission';
6 | import { Card, Column, Content, Header, Icon, Image, Layout, Row, Text } from '@/ui/components';
7 | import { Empty } from '@/ui/components/Empty';
8 | import { fontSizes } from '@/ui/theme/font';
9 | import { useWallet } from '@/ui/utils';
10 | import { useTranslation } from 'react-i18next';
11 |
12 | export default function ConnectedSitesScreen() {
13 | const wallet = useWallet();
14 | const { t } = useTranslation();
15 |
16 | const [sites, setSites] = useState([]);
17 |
18 | const getSites = async () => {
19 | const sites = await wallet.getConnectedSites();
20 | setSites(sites);
21 | };
22 |
23 | useEffect(() => {
24 | getSites();
25 | }, []);
26 |
27 | const handleRemove = async (origin: string) => {
28 | await wallet.removeConnectedSite(origin);
29 | getSites();
30 | };
31 | return (
32 |
33 | {
35 | window.history.go(-1);
36 | }}
37 | title={t('Connected Sites')}
38 | />
39 |
40 |
41 | {sites.length > 0 ? (
42 | sites.map((item, index) => {
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {
54 | handleRemove(item.origin);
55 | }}
56 | />
57 |
58 |
59 |
60 | );
61 | })
62 | ) : (
63 |
64 | )}
65 |
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/ui/pages/Approval/components/SignText.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { Button, Card, Column, Content, Footer, Header, Layout, Row, Text } from '@/ui/components';
4 | import WebsiteBar from '@/ui/components/WebsiteBar';
5 | import { useApproval } from '@/ui/utils';
6 |
7 | interface Props {
8 | params: {
9 | data: {
10 | text: string;
11 | };
12 | session: {
13 | origin: string;
14 | icon: string;
15 | name: string;
16 | };
17 | };
18 | }
19 | export default function SignText({ params: { data, session } }: Props) {
20 | const [getApproval, resolveApproval, rejectApproval] = useApproval();
21 |
22 | const handleCancel = () => {
23 | rejectApproval();
24 | };
25 |
26 | const handleConfirm = () => {
27 | resolveApproval();
28 | };
29 | return (
30 |
31 |
32 |
35 |
36 |
37 |
43 |
44 |
45 |
46 |
55 | {data.text}
56 |
57 |
58 |
59 |
60 |
61 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/ui/pages/Approval/components/SwitchNetwork.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { NETWORK_TYPES } from '@/shared/constant';
4 | import { NetworkType } from '@/shared/types';
5 | import { Button, Card, Column, Content, Footer, Header, Layout, Row, Text } from '@/ui/components';
6 | import WebsiteBar from '@/ui/components/WebsiteBar';
7 | import { useNetworkType } from '@/ui/state/settings/hooks';
8 | import { useApproval } from '@/ui/utils';
9 |
10 | interface Props {
11 | params: {
12 | data: {
13 | networkType: NetworkType;
14 | };
15 | session: {
16 | origin: string;
17 | icon: string;
18 | name: string;
19 | };
20 | };
21 | }
22 |
23 | export default function SwitchNetwork({ params: { data, session } }: Props) {
24 | const networkType = useNetworkType();
25 | const from = NETWORK_TYPES[networkType];
26 | const to = NETWORK_TYPES[data.networkType];
27 |
28 | const [getApproval, resolveApproval, rejectApproval] = useApproval();
29 |
30 | const handleCancel = () => {
31 | rejectApproval('User rejected the request.');
32 | };
33 |
34 | const handleConnect = async () => {
35 | resolveApproval();
36 | };
37 |
38 | return (
39 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/src/ui/pages/Approval/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Connect } from './Connect';
2 | export { default as MultiSignPsbt } from './MultiSignPsbt';
3 | export { default as SignPsbt } from './SignPsbt';
4 | export { default as SignText } from './SignText';
5 | export { default as SwitchNetwork } from './SwitchNetwork';
6 |
7 |
--------------------------------------------------------------------------------
/src/ui/pages/Main/AppTabScreen.tsx:
--------------------------------------------------------------------------------
1 | import { AppInfo } from '@/shared/types';
2 | import { Card, Column, Content, Footer, Header, Image, Layout, Row, Text } from '@/ui/components';
3 | import { NavTabBar } from '@/ui/components/NavTabBar';
4 | import { useAppSummary, useReadApp } from '@/ui/state/accounts/hooks';
5 | import { fontSizes } from '@/ui/theme/font';
6 | import { shortDesc } from '@/ui/utils';
7 |
8 | function AppItem({ info }: { info: AppInfo }) {
9 | const readApp = useReadApp();
10 | return (
11 | {
14 | if (info.url) window.open(info.url);
15 | readApp(info.id);
16 | }}>
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {info.new && }
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default function AppTabScrren() {
39 | const appSummary = useAppSummary();
40 | return (
41 |
42 |
43 |
44 |
45 | {appSummary.apps.map((v) => (
46 |
47 | ))}
48 |
49 |
50 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/ui/pages/Main/BoostScreen.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { getUiType, useApproval, useWallet } from '@/ui/utils';
4 |
5 | import { useNavigate } from '../MainRoute';
6 |
7 | export default function BoostScreen() {
8 | const navigate = useNavigate();
9 | const wallet = useWallet();
10 |
11 | const [getApproval, , rejectApproval] = useApproval();
12 | const loadView = async () => {
13 | const UIType = getUiType();
14 | const isInNotification = UIType.isNotification;
15 | const isInTab = UIType.isTab;
16 | let approval = await getApproval();
17 | if (isInNotification && !approval) {
18 | window.close();
19 | return;
20 | }
21 |
22 | if (!isInNotification) {
23 | await rejectApproval();
24 | approval = undefined;
25 | }
26 |
27 | const isBooted = await wallet.isBooted();
28 | const hasVault = await wallet.hasVault();
29 | const isUnlocked = await wallet.isUnlocked();
30 |
31 | if (!isBooted) {
32 | navigate('WelcomeScreen');
33 | return;
34 | }
35 |
36 | if (!isUnlocked) {
37 | navigate('UnlockScreen');
38 | return;
39 | }
40 |
41 | if (!hasVault) {
42 | navigate('WelcomeScreen');
43 | return;
44 | }
45 |
46 | if ((await wallet.getPreMnemonics()) && !isInNotification && !isInTab) {
47 | navigate('CreateHDWalletScreen', { isImport: false });
48 | return;
49 | }
50 |
51 | const currentAccount = await wallet.getCurrentAccount();
52 |
53 | if (!currentAccount) {
54 | navigate('WelcomeScreen');
55 | return;
56 | } else if (approval) {
57 | navigate('ApprovalScreen');
58 | } else {
59 | navigate('MainScreen');
60 | return;
61 | }
62 | };
63 |
64 | const init = async () => {
65 | const ready = await wallet.isReady();
66 |
67 | if (ready) {
68 | loadView();
69 | } else {
70 | setTimeout(() => {
71 | init();
72 | }, 1000);
73 | }
74 | };
75 |
76 | useEffect(() => {
77 | init();
78 | }, []);
79 |
80 | return ;
81 | }
82 |
--------------------------------------------------------------------------------
/src/ui/pages/Main/DiscoverTabScreen.tsx:
--------------------------------------------------------------------------------
1 | import { Column, Content, Footer, Header, Layout } from '@/ui/components';
2 | import { NavTabBar } from '@/ui/components/NavTabBar';
3 |
4 | export default function DiscoverTabScreen() {
5 | return (
6 |
7 |
8 |
9 |
10 | discover
11 |
12 |
13 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/ui/pages/Main/WelcomeScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable quotes */
2 | import { Button, Column, Content, Layout, Logo, Row, Text } from '@/ui/components';
3 | import { useWallet } from '@/ui/utils';
4 |
5 | import { useNavigate } from '../MainRoute';
6 | import { useTranslation } from 'react-i18next';
7 |
8 | export default function WelcomeScreen() {
9 | const navigate = useNavigate();
10 | const wallet = useWallet();
11 | const { t } = useTranslation();
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 | {
31 | const isBooted = await wallet.isBooted();
32 | if (isBooted) {
33 | navigate('CreateHDWalletScreen', { isImport: false });
34 | } else {
35 | navigate('CreatePasswordScreen', { isNewAccount: true });
36 | }
37 | }}
38 | />
39 | {
43 | const isBooted = await wallet.isBooted();
44 | if (isBooted) {
45 | navigate('CreateHDWalletScreen', { isImport: true });
46 | } else {
47 | navigate('CreatePasswordScreen', { isNewAccount: false });
48 | }
49 | }}
50 | />
51 |
52 |
53 |
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/src/ui/pages/Settings/EditAccountNameScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import React, { useMemo, useState } from 'react';
4 | import { useTranslation } from 'react-i18next';
5 | import { useLocation } from 'react-router-dom';
6 |
7 | import { Account } from '@/shared/types';
8 | import { Button, Content, Header, Input, Layout } from '@/ui/components';
9 | import { accountActions } from '@/ui/state/accounts/reducer';
10 | import { useAppDispatch } from '@/ui/state/hooks';
11 | import { keyringsActions } from '@/ui/state/keyrings/reducer';
12 | import { useWallet } from '@/ui/utils';
13 |
14 | export default function EditAccountNameScreen() {
15 | const { t } = useTranslation();
16 |
17 | const { state } = useLocation();
18 | const { account } = state as {
19 | account: Account;
20 | };
21 |
22 | const wallet = useWallet();
23 | const [alianName, setAlianName] = useState('');
24 | const dispatch = useAppDispatch();
25 | const handleOnClick = async () => {
26 | const newAccount = await wallet.setAccountAlianName(account, alianName);
27 | dispatch(keyringsActions.updateAccountName(newAccount));
28 | dispatch(accountActions.updateAccountName(newAccount));
29 | window.history.go(-1);
30 | };
31 |
32 | const handleOnKeyUp = (e: React.KeyboardEvent) => {
33 | if ('Enter' == e.key) {
34 | handleOnClick();
35 | }
36 | };
37 |
38 | const validName = useMemo(() => {
39 | if (alianName.length == 0) {
40 | return false;
41 | }
42 | return true;
43 | }, [alianName]);
44 | return (
45 |
46 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/ui/pages/Settings/EditContactNameScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import React, { useMemo, useState } from 'react';
4 | import { useTranslation } from 'react-i18next';
5 | import { useLocation } from 'react-router-dom';
6 |
7 | import { ContactBookItem } from '@/background/service/contactBook';
8 | import { Button, Content, Header, Input, Layout } from '@/ui/components';
9 | import { RemoveContactPopover } from '@/ui/components/RemoveContactPopover';
10 | import { shortAddress, useWallet } from '@/ui/utils';
11 |
12 | export default function EditContactNameScreen() {
13 | const { t } = useTranslation();
14 |
15 | const { state } = useLocation();
16 | const { account } = state as {
17 | account: ContactBookItem;
18 | };
19 |
20 | const wallet = useWallet();
21 | const [alianName, setAlianName] = useState('');
22 | const [removeVisible, setRemoveVisible] = useState(false);
23 | const handleOnClick = async () => {
24 | // const newAccount = await wallet.setAccountAlianName(account, alianName);
25 | const newContact = {
26 | ...account,
27 | name: alianName
28 | };
29 | await wallet.updateContact(newContact);
30 | // dispatch(keyringsActions.updateAccountName(newAccount));
31 | // dispatch(accountActions.updateAccountName(newAccount));
32 | window.history.go(-1);
33 | };
34 |
35 | const handleOnKeyUp = (e: React.KeyboardEvent) => {
36 | if ('Enter' == e.key) {
37 | handleOnClick();
38 | }
39 | };
40 |
41 | const validName = useMemo(() => {
42 | if (alianName.length == 0) {
43 | return false;
44 | }
45 | return true;
46 | }, [alianName]);
47 | return (
48 |
49 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/ui/pages/Settings/EditWalletNameScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import React, { useMemo, useState } from 'react';
4 | import { useLocation } from 'react-router-dom';
5 |
6 | import { WalletKeyring } from '@/shared/types';
7 | import { Button, Column, Content, Header, Input, Layout } from '@/ui/components';
8 | import { useAppDispatch } from '@/ui/state/hooks';
9 | import { keyringsActions } from '@/ui/state/keyrings/reducer';
10 | import { useWallet } from '@/ui/utils';
11 | import { useTranslation } from 'react-i18next';
12 |
13 | export default function EditWalletNameScreen() {
14 | const { t } = useTranslation();
15 | const { state } = useLocation();
16 | const { keyring } = state as {
17 | keyring: WalletKeyring;
18 | };
19 |
20 | const wallet = useWallet();
21 | const [alianName, setAlianName] = useState('');
22 | const dispatch = useAppDispatch();
23 | const handleOnClick = async () => {
24 | const newKeyring = await wallet.setKeyringAlianName(keyring, alianName || keyring.alianName);
25 | dispatch(keyringsActions.updateKeyringName(newKeyring));
26 | window.history.go(-1);
27 | };
28 |
29 | const handleOnKeyUp = (e: React.KeyboardEvent) => {
30 | if ('Enter' == e.key) {
31 | handleOnClick();
32 | }
33 | };
34 |
35 | const isValidName = useMemo(() => {
36 | if (alianName.length == 0) {
37 | return false;
38 | }
39 | return true;
40 | }, [alianName]);
41 |
42 | return (
43 |
44 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/ui/pages/Settings/LanguageTypeScreen.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { Card, Column, Content, Header, Icon, Layout, Row, Text } from '@/ui/components';
4 | import { useTools } from '@/ui/components/ActionComponent';
5 | import { useReloadAccounts } from '@/ui/state/accounts/hooks';
6 | import { useChangeLocaleCallback, useLocale } from '@/ui/state/settings/hooks';
7 | import { CheckCircleFilled } from '@ant-design/icons';
8 | import { useTranslation } from 'react-i18next';
9 | import { Languages } from '../../../../build/languages.js';
10 |
11 | export default function LanguageTypeScreen() {
12 | const changeLanguageType = useChangeLocaleCallback();
13 | const locale = useLocale();
14 | const reloadAccounts = useReloadAccounts();
15 | const tools = useTools();
16 | const { t } = useTranslation();
17 | return (
18 |
19 | {
21 | window.history.go(-1);
22 | }}
23 | title={t('Switch Language')}
24 | />
25 |
26 |
27 | {Languages &&
28 | Languages.length > 0 &&
29 | Languages.map((item, index) => {
30 | return (
31 |
32 | {
35 | if (item.symbol == locale) {
36 | return;
37 | }
38 | await changeLanguageType(item.symbol);
39 | reloadAccounts();
40 | // navigate('MainScreen');
41 | tools.toastSuccess(t('Language type changed'));
42 | }}>
43 |
44 | {item.symbol == locale && (
45 |
46 |
47 |
48 | )}
49 |
50 |
51 |
52 | {/* */}
53 |
54 |
55 |
56 | );
57 | })}
58 |
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/ui/pages/Settings/UpgradeNoticeScreen.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Card, Column, Content, Header, Layout } from '@/ui/components';
2 | import { useTranslation } from 'react-i18next';
3 |
4 | const UPGRADE_NOTICE = '...';
5 | export default function UpgradeNoticeScreen() {
6 | const { t } = useTranslation();
7 | return (
8 |
9 | {
11 | window.history.go(-1);
12 | }}
13 | title={t('Notice')}
14 | />
15 |
16 |
17 |
18 |
27 | {UPGRADE_NOTICE}
28 |
29 |
30 | {
34 | window.history.go(-1);
35 | }}
36 | />
37 |
38 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/ui/pages/Test/TestScreen.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Column, Content, Layout } from '@/ui/components';
3 | import { AddressTypeCard2 } from '@/ui/components/AddressTypeCard';
4 |
5 | export default function TestScreen() {
6 | return ;
7 | }
8 | function TestAddressTypeCard() {
9 | const items = [
10 | { address: 'kaspa:1234567890', path: 'm/84\'/0\'/0\'/0/0', sompi: 100 }
11 | ];
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/ui/pages/Wallet/FiatPayScreen.tsx:
--------------------------------------------------------------------------------
1 | import { Column, Content, Header, Layout, TextArea } from '@/ui/components';
2 | import { fontSizes } from '@/ui/theme/font';
3 | import { useTranslation } from 'react-i18next';
4 |
5 | const disclaimStr = 'buy kas';
6 | export default function FiatPayScreen() {
7 | const { t } = useTranslation();
8 | return (
9 |
10 | {
12 | window.history.go(-1);
13 | }}
14 | title={t('Buy')}
15 | />
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/ui/pages/Wallet/ReceiveScreen/index.less:
--------------------------------------------------------------------------------
1 | .receive-content {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | padding: 0.625rem 3.125rem;
6 | width: 31.25rem;
7 | // height:9.31rem;
8 | .frame1 {
9 | display: flex;
10 | flex-direction: row;
11 | justify-content: center;
12 | align-items: center;
13 | padding: 0.625rem 0px;
14 | gap: 0.625rem;
15 |
16 | width: 25rem;
17 | height: 2.625rem;
18 |
19 | border-radius: 0.3rem;
20 |
21 | .profile {
22 | display: flex;
23 | flex-direction: column;
24 | justify-content: center;
25 | align-items: center;
26 | padding: 0px;
27 |
28 | width: 0.98rem;
29 | height: 1.37rem;
30 | .icon {
31 | width: 0.98rem;
32 | height: 1.125rem;
33 | }
34 | }
35 |
36 | span {
37 | font-family: 'Inter';
38 | font-style: normal;
39 | font-weight: 700;
40 | font-size: 1.125rem;
41 | line-height: 1.37rem;
42 | /* identical to box height */
43 |
44 | display: flex;
45 | align-items: center;
46 |
47 | color: #ffffff;
48 |
49 | text-shadow: 3px 3px 2px rgba(0, 0, 0, 0.1);
50 | }
51 | }
52 | }
53 |
54 | .receive-tip {
55 | display: flex;
56 | flex-direction: column;
57 | justify-content: center;
58 | align-items: center;
59 | padding: 0px;
60 | gap: 0.625rem;
61 | width: 31.25rem;
62 | height: 1.18rem;
63 | span {
64 | height: 1.18rem;
65 |
66 | font-family: 'Inter';
67 | font-style: normal;
68 | font-weight: 400;
69 | font-size: 1rem;
70 | line-height: 1.18rem;
71 | display: flex;
72 | align-items: center;
73 |
74 | color: #aaaaaa;
75 |
76 | text-shadow: 3px 3px 2px rgba(0, 0, 0, 0.1);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/ui/pages/Wallet/ReceiveScreen/index.tsx:
--------------------------------------------------------------------------------
1 | import QRCode from 'qrcode.react';
2 |
3 | import { AddressBar, Column, Content, Header, Icon, Layout, Row, Text } from '@/ui/components';
4 | import { useAccountAddress, useCurrentAccount } from '@/ui/state/accounts/hooks';
5 | import { sizes } from '@/ui/theme/spacing';
6 |
7 | import { useTranslation } from 'react-i18next';
8 | import './index.less';
9 |
10 | export default function ReceiveScreen() {
11 | const currentAccount = useCurrentAccount();
12 | const address = useAccountAddress();
13 | const { t } = useTranslation();
14 |
15 | return (
16 |
17 | {
19 | window.history.go(-1);
20 | }}
21 | title={t('Address')}
22 | />
23 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/ui/pages/Wallet/TxConfirmScreen.tsx:
--------------------------------------------------------------------------------
1 | import { RawTxInfo, TxType } from '@/shared/types';
2 | import { Header } from '@/ui/components';
3 | import { useLocationState } from '@/ui/utils';
4 |
5 | import { usePushKaspaTxCallback } from '@/ui/state/transactions/hooks';
6 | import { SignPsbt } from '../Approval/components';
7 | import { useNavigate } from '../MainRoute';
8 |
9 | interface LocationState {
10 | rawTxInfo: RawTxInfo;
11 | }
12 |
13 | export default function TxConfirmScreen() {
14 | const { rawTxInfo } = useLocationState();
15 | const navigate = useNavigate();
16 | const pushKaspaTx = usePushKaspaTxCallback();
17 | return (
18 | {
22 | // window.history.go(-1);
23 | navigate('TxCreateScreen', { rawTxInfo });
24 | }}
25 | />
26 | }
27 | params={{ data: { psbtHex: rawTxInfo.psbtHex, type: TxType.SEND_KASPA, rawTxInfo } }}
28 | handleCancel={() => {
29 | // window.history.go(-1);
30 | navigate('TxCreateScreen', { rawTxInfo });
31 | }}
32 | handleConfirm={() => {
33 | pushKaspaTx(rawTxInfo.rawtx).then(({ success, txid, error }) => {
34 | if (success) {
35 | navigate('TxSuccessScreen', { txid, rawtx: rawTxInfo.rawtx });
36 | } else {
37 | navigate('TxFailScreen', { error });
38 | }
39 | });
40 | }}
41 | />
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/ui/pages/Wallet/TxFailScreen.tsx:
--------------------------------------------------------------------------------
1 | import { Column, Content, Header, Icon, Layout, Row, Text } from '@/ui/components';
2 | import { colors } from '@/ui/theme/colors';
3 | import { useLocationState } from '@/ui/utils';
4 | import { useEffect, useState } from 'react';
5 | import { useTranslation } from 'react-i18next';
6 |
7 | export default function TxFailScreen() {
8 | const { t } = useTranslation();
9 | const { error } = useLocationState<{ error: string }>();
10 | const [isMassResaon, setIsMassResaon] = useState(false);
11 | useEffect(() => {
12 | if (error && error.includes('is larger than max allowed size of 100000')) {
13 | setIsMassResaon(true);
14 | }
15 | }, [error]);
16 | return (
17 |
18 | {
20 | window.history.go(-1);
21 | }}
22 | />
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {isMassResaon && }
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/src/ui/pages/Wallet/TxSuccessScreen.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Column, Content, Footer, Header, Icon, Layout, Row, Text } from '@/ui/components';
2 | import { useNavigate } from '@/ui/pages/MainRoute';
3 | import { useBlockstreamUrl } from '@/ui/state/settings/hooks';
4 | import { colors } from '@/ui/theme/colors';
5 | import { spacing } from '@/ui/theme/spacing';
6 | import { shortAddress, sompiToAmount, useLocationState } from '@/ui/utils';
7 | import { ExportOutlined } from '@ant-design/icons';
8 | import { useMemo } from 'react';
9 | import { useTranslation } from 'react-i18next';
10 |
11 | interface LocationState {
12 | txid: string;
13 | rawtx: string;
14 | }
15 |
16 | export default function TxSuccessScreen() {
17 | const { t } = useTranslation();
18 | const { txid, rawtx } = useLocationState();
19 | const navigate = useNavigate();
20 | const blockstreamUrl = useBlockstreamUrl();
21 | const toAddrss = useMemo(() => {
22 | const result = JSON.parse(rawtx);
23 | return result.to;
24 | }, []);
25 | const inputAmount = useMemo(() => {
26 | const result = JSON.parse(rawtx);
27 | const inputAmountSompi = result.amountSompi;
28 | return sompiToAmount(inputAmountSompi);
29 | }, []);
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | {
46 | window.open(`${blockstreamUrl}/transaction/${txid}`);
47 | }}>
48 |
49 |
50 |
51 |
52 |
53 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/ui/pages/index.module.less:
--------------------------------------------------------------------------------
1 | .antInput {
2 | color: #aaaaaa !important;
3 | }
4 |
5 | .antInputActive {
6 | color: white !important;
7 | }
8 |
--------------------------------------------------------------------------------
/src/ui/state/global/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from '@reduxjs/toolkit';
2 |
3 | // fired once when the app reloads but before the app renders
4 | // allows any updates to be applied to store data loaded from localStorage
5 | export const updateVersion = createAction('global/updateVersion');
6 |
--------------------------------------------------------------------------------
/src/ui/state/global/reducer.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | /* eslint-disable no-unused-vars */
3 | import { createSlice } from '@reduxjs/toolkit';
4 |
5 | import { updateVersion } from '../global/actions';
6 |
7 | export type TabOption = 'home' | 'mint' | 'app' | 'settings';
8 |
9 | export interface GlobalState {
10 | tab: TabOption;
11 | isUnlocked: boolean;
12 | isReady: boolean;
13 | isBooted: boolean;
14 | }
15 |
16 | export const initialState: GlobalState = {
17 | tab: 'home',
18 | isUnlocked: false,
19 | isReady: false,
20 | isBooted: false
21 | };
22 |
23 | const slice = createSlice({
24 | name: 'global',
25 | initialState,
26 | reducers: {
27 | reset(state) {
28 | return initialState;
29 | },
30 | update(
31 | state,
32 | action: {
33 | payload: {
34 | tab?: TabOption;
35 | isUnlocked?: boolean;
36 | isReady?: boolean;
37 | isBooted?: boolean;
38 | };
39 | }
40 | ) {
41 | const { payload } = action;
42 | state = Object.assign({}, state, payload);
43 | return state;
44 | }
45 | },
46 | extraReducers: (builder) => {
47 | builder.addCase(updateVersion, (state) => {
48 | // todo
49 | });
50 | }
51 | });
52 |
53 | export const globalActions = slice.actions;
54 | export default slice.reducer;
55 |
--------------------------------------------------------------------------------
/src/ui/state/hooks.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
2 |
3 | import { AppDispatch, AppState } from './index';
4 |
5 | export const useAppDispatch = () => useDispatch();
6 | export const useAppSelector: TypedUseSelectorHook = useSelector;
7 |
--------------------------------------------------------------------------------
/src/ui/state/index.ts:
--------------------------------------------------------------------------------
1 | import { load, save } from 'redux-localstorage-simple';
2 |
3 | import { configureStore } from '@reduxjs/toolkit';
4 | import { setupListeners } from '@reduxjs/toolkit/query/react';
5 |
6 | import accounts from './accounts/reducer';
7 | import { updateVersion } from './global/actions';
8 | import global from './global/reducer';
9 | import keyrings from './keyrings/reducer';
10 | import settings from './settings/reducer';
11 | import transactions from './transactions/reducer';
12 |
13 | const PERSISTED_KEYS: string[] = ['ui'];
14 | const store = configureStore({
15 | reducer: {
16 | accounts,
17 | transactions,
18 | settings,
19 | global,
20 | keyrings,
21 | },
22 | middleware: (getDefaultMiddleware) =>
23 | getDefaultMiddleware({ thunk: true }).concat(save({ states: PERSISTED_KEYS, debounce: 1000 })),
24 | preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: true })
25 | });
26 |
27 | store.dispatch(updateVersion());
28 |
29 | setupListeners(store.dispatch);
30 |
31 | export default store;
32 |
33 | export type AppState = ReturnType;
34 | export type AppDispatch = typeof store.dispatch;
35 |
--------------------------------------------------------------------------------
/src/ui/state/keyrings/hooks.ts:
--------------------------------------------------------------------------------
1 | import { AppState } from '..';
2 | import { useAppSelector } from '../hooks';
3 |
4 | export function useKeyringsState(): AppState['keyrings'] {
5 | return useAppSelector((state) => state.keyrings);
6 | }
7 |
8 | export function useKeyrings() {
9 | const keyringsState = useKeyringsState();
10 | return keyringsState.keyrings;
11 | }
12 |
13 | export function useCurrentKeyring() {
14 | const keyringsState = useKeyringsState();
15 | return keyringsState.current;
16 | }
17 |
--------------------------------------------------------------------------------
/src/ui/state/keyrings/reducer.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import { Account, AddressType, WalletKeyring } from '@/shared/types';
4 | import { createSlice } from '@reduxjs/toolkit';
5 |
6 | import { updateVersion } from '../global/actions';
7 |
8 | export interface KeyringsState {
9 | keyrings: WalletKeyring[];
10 | current: WalletKeyring;
11 | }
12 |
13 | const initialKeyring: WalletKeyring = {
14 | key: '',
15 | index: 0,
16 | type: '',
17 | addressType: AddressType.KASPA_44_111111,
18 | accounts: [],
19 | alianName: '',
20 | hdPath: '',
21 | // kaspa amount
22 | balanceKas: 0
23 | };
24 |
25 | export const initialState: KeyringsState = {
26 | keyrings: [],
27 | current: initialKeyring
28 | };
29 |
30 | const slice = createSlice({
31 | name: 'keyrings',
32 | initialState,
33 | reducers: {
34 | setCurrent(state, action: { payload: WalletKeyring }) {
35 | const { payload } = action;
36 | state.current = payload || initialKeyring;
37 | },
38 | setKeyrings(state, action: { payload: WalletKeyring[] }) {
39 | const { payload } = action;
40 | state.keyrings = payload;
41 | },
42 | setKeyringBalanceKas(state, action: { payload: { key: string; balanceKas: number } }) {
43 | const { payload } = action;
44 | for (let i = 0; i < state.keyrings.length; i++) {
45 | if (state.keyrings[i].key === payload.key) {
46 | state.keyrings[i].balanceKas = payload.balanceKas;
47 | }
48 | }
49 | if (state.current.key === payload.key) {
50 | state.current.balanceKas = payload.balanceKas;
51 | }
52 | },
53 |
54 | reset(state) {
55 | return initialState;
56 | },
57 |
58 | updateKeyringName(state, action: { payload: WalletKeyring }) {
59 | const keyring = action.payload;
60 | if (state.current.key === keyring.key) {
61 | state.current.alianName = keyring.alianName;
62 | }
63 | state.keyrings.forEach((v) => {
64 | if (v.key === keyring.key) {
65 | v.alianName = keyring.alianName;
66 | }
67 | });
68 | },
69 |
70 | updateAccountName(state, action: { payload: Account }) {
71 | const account = action.payload;
72 |
73 | state.current.accounts.forEach((v) => {
74 | if (v.key === account.key) {
75 | v.alianName = account.alianName;
76 | }
77 | });
78 |
79 | state.keyrings.forEach((v) => {
80 | v.accounts.forEach((w) => {
81 | if (w.key === account.key) {
82 | w.alianName = account.alianName;
83 | }
84 | });
85 | });
86 | }
87 | },
88 | extraReducers: (builder) => {
89 | builder.addCase(updateVersion, (state) => {
90 | // todo
91 | });
92 | }
93 | });
94 |
95 | export const keyringsActions = slice.actions;
96 | export default slice.reducer;
97 |
--------------------------------------------------------------------------------
/src/ui/state/settings/reducer.ts:
--------------------------------------------------------------------------------
1 | import { AddressType, NetworkType, WalletConfig } from '@/shared/types';
2 | import { createSlice } from '@reduxjs/toolkit';
3 |
4 | import { NETWORK_TYPES } from '@/shared/constant';
5 | import { updateVersion } from '../global/actions';
6 |
7 | export interface SettingsState {
8 | locale: string;
9 | addressType: AddressType;
10 | networkType: NetworkType;
11 | rpcLinks:typeof NETWORK_TYPES;
12 | walletConfig: WalletConfig;
13 | skippedVersion: string;
14 | }
15 |
16 | export const initialState: SettingsState = {
17 | locale: 'English',
18 | addressType: AddressType.KASPA_44_111111,
19 | networkType: NetworkType.Mainnet,
20 | rpcLinks:NETWORK_TYPES,
21 | walletConfig: {
22 | version: '',
23 | moonPayEnabled: false,
24 | statusMessage: ''
25 | },
26 | skippedVersion: ''
27 | };
28 |
29 | const slice = createSlice({
30 | name: 'settings',
31 | initialState,
32 | reducers: {
33 | // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
34 | reset(state) {
35 | return initialState;
36 | },
37 | updateSettings(
38 | state,
39 | action: {
40 | payload: {
41 | locale?: string;
42 | addressType?: AddressType;
43 | networkType?: NetworkType;
44 | rpcLinks?:typeof NETWORK_TYPES
45 | walletConfig?: WalletConfig;
46 | skippedVersion?: string;
47 | };
48 | }
49 | ) {
50 | const { payload } = action;
51 | state = Object.assign({}, state, payload);
52 | return state;
53 | }
54 | },
55 | extraReducers: (builder) => {
56 | builder.addCase(updateVersion, (state) => {
57 | // todo
58 | if (!state.networkType) {
59 | state.networkType = NetworkType.Mainnet;
60 | }
61 | });
62 | }
63 | });
64 |
65 | export const settingsActions = slice.actions;
66 | export default slice.reducer;
67 |
--------------------------------------------------------------------------------
/src/ui/state/transactions/reducer.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | /* eslint-disable @typescript-eslint/no-explicit-any */
4 | import { IKaspaUTXOWithoutBigint, ITransactionInfo } from '@/shared/types';
5 | import { createSlice } from '@reduxjs/toolkit';
6 |
7 | import { updateVersion } from '../global/actions';
8 |
9 | export interface KaspaTx {
10 | fromAddress: string;
11 | toAddress: string;
12 | toSompi: number;
13 | rawtx: string;
14 | txid: string;
15 | fee: number;
16 | estimateFee: number;
17 | changeSompi: number;
18 | sending: boolean;
19 | autoAdjust: boolean;
20 | psbtHex: string;
21 | feeRate: number;
22 | toDomain: string;
23 | }
24 |
25 | export interface TransactionsState {
26 | kaspaTx: KaspaTx;
27 | utxos: IKaspaUTXOWithoutBigint[];
28 | kasUtxos:string;
29 | txActivities: ITransactionInfo[];
30 | incomingTx: boolean
31 | }
32 |
33 | export const initialState: TransactionsState = {
34 | kaspaTx: {
35 | fromAddress: '',
36 | toAddress: '',
37 | toSompi: 0,
38 | rawtx: '',
39 | txid: '',
40 | fee: 0,
41 | estimateFee: 0,
42 | changeSompi: 0,
43 | sending: false,
44 | autoAdjust: false,
45 | psbtHex: '',
46 | feeRate: 5,
47 | toDomain: ''
48 | },
49 | utxos: [],
50 | kasUtxos:'',
51 | txActivities: [],
52 | incomingTx:false
53 | };
54 |
55 | const slice = createSlice({
56 | name: 'transactions',
57 | initialState,
58 | reducers: {
59 | updateKaspaTx(
60 | state,
61 | action: {
62 | payload: {
63 | fromAddress?: string;
64 | toAddress?: string;
65 | toSompi?: number;
66 | changeSompi?: number;
67 | rawtx?: string;
68 | txid?: string;
69 | fee?: number;
70 | estimateFee?: number;
71 | sending?: boolean;
72 | autoAdjust?: boolean;
73 | psbtHex?: string;
74 | feeRate?: number;
75 | toDomain?: string;
76 | };
77 | }
78 | ) {
79 | const { payload } = action;
80 | state.kaspaTx = Object.assign({}, state.kaspaTx, payload);
81 | },
82 | setUtxos(state, action: { payload: any[] }) {
83 | state.utxos = action.payload;
84 | },
85 | setTxActivities(state, action: { payload: any[] }) {
86 | state.txActivities = action.payload;
87 | },
88 | setIncomingTx(state, action: { payload: boolean }) {
89 | state.incomingTx = action.payload;
90 | },
91 | setKasUtxos(state, action: { payload: string }) {
92 | state.kasUtxos = action.payload;
93 | },
94 | reset(state) {
95 | return initialState;
96 | }
97 | },
98 |
99 | extraReducers: (builder) => {
100 | // eslint-disable-next-line @typescript-eslint/no-empty-function
101 | builder.addCase(updateVersion, (state) => {});
102 | }
103 | });
104 |
105 | export const transactionsActions = slice.actions;
106 | export default slice.reducer;
107 |
--------------------------------------------------------------------------------
/src/ui/styles/font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: ProtoMono-Bold;
3 | src: url('./fonts/ProtoMono-Bold.otf');
4 | }
5 |
6 | @font-face {
7 | font-family: ProtoMono-Regular;
8 | src: url('./fonts/ProtoMono-Regular.otf');
9 | }
10 |
11 | @font-face {
12 | font-family: ProtoMono-Light;
13 | src: url('./fonts/ProtoMono-Light.otf');
14 | }
15 |
16 | @font-face {
17 | font-family: Inter-Light;
18 | font-style: normal;
19 | font-weight: 300;
20 | font-display: swap;
21 | src: url('./fonts/Inter-Light.woff2') format('woff2');
22 | }
23 |
24 | @font-face {
25 | font-family: Inter-Regular;
26 | font-style: normal;
27 | font-weight: 400;
28 | font-display: swap;
29 | src: url('./fonts/Inter-Regular.woff2') format('woff2');
30 | }
31 |
32 | @font-face {
33 | font-family: Inter-Bold;
34 | font-style: normal;
35 | font-weight: 600;
36 | font-display: swap;
37 | src: url('./fonts/Inter-SemiBold.woff2') format('woff2');
38 | }
39 |
--------------------------------------------------------------------------------
/src/ui/styles/fonts/Inter-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/src/ui/styles/fonts/Inter-Light.woff2
--------------------------------------------------------------------------------
/src/ui/styles/fonts/Inter-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/src/ui/styles/fonts/Inter-Regular.woff2
--------------------------------------------------------------------------------
/src/ui/styles/fonts/Inter-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/src/ui/styles/fonts/Inter-SemiBold.woff2
--------------------------------------------------------------------------------
/src/ui/styles/fonts/ProtoMono-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/src/ui/styles/fonts/ProtoMono-Bold.otf
--------------------------------------------------------------------------------
/src/ui/styles/fonts/ProtoMono-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/src/ui/styles/fonts/ProtoMono-Light.otf
--------------------------------------------------------------------------------
/src/ui/styles/fonts/ProtoMono-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasware-wallet/extension/90dd7af3479b78891592f87afcbddccbde3a9e00/src/ui/styles/fonts/ProtoMono-Regular.otf
--------------------------------------------------------------------------------
/src/ui/styles/global.less:
--------------------------------------------------------------------------------
1 | @import url('./font.css');
2 | .popover-container {
3 | position: fixed;
4 | top: 0;
5 | bottom: 0;
6 | left: 0;
7 | right: 0;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | z-index: 10;
12 | }
13 |
14 | iframe {
15 | overflow-clip-margin: content-box !important;
16 | border-width: 0px;
17 | border-style: inset;
18 | border-color: initial;
19 | border-image: initial;
20 | overflow: clip !important;
21 | }
22 |
23 | .ant-checkbox-inner {
24 | width: 15px !important;
25 | height: 15px !important;
26 | background-color: rgba(0, 0, 0, 0) !important;
27 | border-width: 1px !important;
28 | border-color: rgba(255, 255, 255, 0.5) !important;
29 | }
30 |
31 | .ant-checkbox {
32 | width: 15px !important;
33 | height: 15px !important;
34 |
35 | background-color: #383535 !important;
36 | }
37 |
38 | .ant-checkbox-checked {
39 | background-color: #ffde04 !important;
40 | }
41 |
42 | div {
43 | border: 0px solid;
44 | }
45 |
46 | // For scrollbar
47 | ::-webkit-scrollbar {
48 | width: 4px;
49 | height: 4px;
50 | }
51 |
52 | ::-webkit-scrollbar-thumb {
53 | border-radius: 3px;
54 | box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.1);
55 | background: rgba(255, 255, 255, 0.854);
56 | }
57 |
58 | ::-webkit-scrollbar-track {
59 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1) inset;
60 | }
61 |
62 | ::-webkit-scrollbar-corner {
63 | background: transparent;
64 | }
65 |
66 | .ant-tooltip {
67 | max-width: 300px !important;
68 | }
69 |
70 | .recent-account-select {
71 | display: flex;
72 | flex-direction: row;
73 | // align-items: center;
74 | justify-content: space-between;
75 |
76 | padding: 2px 2px;
77 | background: #2a2626;
78 | border: none;
79 | border-radius: 0.3rem;
80 | cursor: pointer;
81 |
82 | &:hover {
83 | background: #383535;
84 | border: none;
85 | border-radius: 0.3rem;
86 | }
87 | }
88 | .card-select {
89 | &:hover {
90 | background: #383535 !important;
91 | border: none !important;
92 | border-radius: 0.3rem !important;
93 | }
94 | }
95 |
96 | .column-select {
97 | padding: 5px 5px;
98 | &:hover {
99 | background: #383535 !important;
100 | border: none !important;
101 | border-radius: 0.3rem !important;
102 | }
103 | }
104 |
105 | .text-select {
106 | &:hover {
107 | text-decoration: underline;
108 | }
109 | }
--------------------------------------------------------------------------------
/src/ui/theme/colors.ts:
--------------------------------------------------------------------------------
1 | // TODO: write documentation for colors and palette in own markdown file and add links from here
2 |
3 | const palette = {
4 | white: '#ffffff',
5 | white_muted: 'rgba(255, 255, 255, 0.5)',
6 | black: '#000000',
7 | black_muted: 'rgba(0, 0, 0, 0.5)',
8 | black_muted2: 'rgba(0, 0, 0, 0.)',
9 |
10 | dark: '#1E283C',
11 | grey: '#495361',
12 | light: '#A2A4AA',
13 |
14 | black_dark: '#2a2626',
15 |
16 | green_dark: '#379a29',
17 | green: '#41B530',
18 | green_light: '#5ec04f',
19 |
20 | yellow_dark: '#d5ac00',
21 | yellow: 'rgb(253,224,71)',
22 | yellow_light: '#fcd226',
23 |
24 | red_dark: '#c92b40',
25 | red: '#ED334B',
26 | red_light: '#f05266',
27 |
28 | blue_dark: '#1461d1',
29 | blue: '#1872F6',
30 | blue_light: '#c6dcfd',
31 |
32 | orange_dark: '#d9691c',
33 | orange: '#FF7B21',
34 | orange_light: '#ff8f42',
35 |
36 | gold: '#eac249',
37 |
38 | aqua_dark:'#339999',
39 | aqua:'#48e3c5',
40 | aqua_light:'#adfff3',
41 | };
42 |
43 | export const colors = Object.assign({}, palette, {
44 | transparent: 'rgba(0, 0, 0, 0)',
45 |
46 | text: palette.white,
47 |
48 | textDim: palette.white_muted,
49 |
50 | background: '#D8E0EF',
51 |
52 | error: '#e52937',
53 |
54 | danger: palette.red,
55 |
56 | card: '#262222',
57 | warning: palette.orange,
58 | primary: palette.yellow,
59 |
60 | bg2: '#2a2a2a',
61 | bg3: '#434242',
62 | bg4: '#383535',
63 |
64 | border: 'rgba(255,255,255,0.1)',
65 |
66 | icon_yellow:'#FFBA33'
67 | });
68 |
69 | export type ColorTypes = keyof typeof colors;
70 |
--------------------------------------------------------------------------------
/src/ui/theme/font.ts:
--------------------------------------------------------------------------------
1 | export const fontSizes = {
2 | xxxl: 48,
3 | xxl: 24,
4 | xl: 20,
5 | lg: 18,
6 | md: 16,
7 | sm: 14,
8 | xs: 12,
9 | xxs: 10,
10 |
11 | banner: 30,
12 | title: 26,
13 | tiny: 14,
14 | tinyShort: 12,
15 |
16 | logo: 32,
17 | icon: 14,
18 | iconMiddle: 20,
19 | iconLarge: 32
20 | };
21 |
--------------------------------------------------------------------------------
/src/ui/theme/spacing.ts:
--------------------------------------------------------------------------------
1 | /**
2 | Use these spacings for margins/paddings and other whitespace throughout your app.
3 | */
4 | export const spacing = {
5 | micro: 2,
6 | tiny: 4,
7 | extraSmall: 8,
8 | small: 12,
9 | medium: 16,
10 | large: 24,
11 | extraLarge: 32,
12 | huge: 48,
13 | massive: 64
14 | } as const;
15 |
16 | export const spacingGap = {
17 | xxl: 40,
18 | xl: 20,
19 | lg: 12,
20 | md: 8,
21 | sm: 4,
22 | xs: 2,
23 | zero: 0
24 | };
25 |
26 | export const sizes = {
27 | qrcode: 180
28 | };
29 |
30 | export type Size = keyof typeof sizes;
31 |
32 | export type Gap = keyof typeof spacingGap;
33 |
34 | export type Spacing = keyof typeof spacing;
35 |
--------------------------------------------------------------------------------
/src/ui/theme/typography.ts:
--------------------------------------------------------------------------------
1 | const fonts = {
2 | ProtoMono: {
3 | bold: 'ProtoMono-Bold',
4 | regular: 'ProtoMono-Regular',
5 | light: 'ProtoMono-Light'
6 | },
7 | Inter: {
8 | bold: 'Inter-Bold',
9 | regular: 'Inter-Regular',
10 | light: 'Inter-Light'
11 | }
12 | };
13 |
14 | export const typography = {
15 | /**
16 | * The fonts are available to use, but prefer using the semantic name.
17 | */
18 | fonts,
19 | /**
20 | * The primary font. Used in most places.
21 | */
22 | primary: fonts.Inter
23 | };
24 |
--------------------------------------------------------------------------------
/src/ui/utils/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { initReactI18next } from 'react-i18next';
3 |
4 | export const fetchLocale = async (locale) => {
5 | const res = await fetch(`./_locales/${locale}/messages.json`);
6 | const data: Record = await res.json();
7 | return Object.keys(data).reduce((res, key) => {
8 | return {
9 | ...res,
10 | [key.replace(/__/g, ' ')]: data[key].message
11 | };
12 | }, {});
13 | };
14 |
15 | i18n
16 | .use(initReactI18next) // passes i18n down to react-i18next
17 | .init({
18 | fallbackLng: 'en',
19 | defaultNS: 'translations',
20 | interpolation: {
21 | escapeValue: false // react already safes from xss
22 | }
23 | });
24 |
25 | export const I18N_NS = 'translations';
26 |
27 | export const addResourceBundle = async (locale: string) => {
28 | if (i18n.hasResourceBundle(locale, I18N_NS)) return;
29 | const bundle = await fetchLocale(locale);
30 |
31 | i18n.addResourceBundle(locale, 'translations', bundle);
32 | };
33 |
34 | addResourceBundle('en');
35 |
36 | i18n.on('languageChanged', function (lng: string) {
37 | addResourceBundle(lng);
38 | });
39 |
40 | export default i18n;
41 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "baseUrl": "./",
6 | "paths": {
7 | "@/*": ["./src/*"]
8 | },
9 | "noImplicitAny": false,
10 | "downlevelIteration": true,
11 | "types": ["chrome", "node", "react", "react-dom"],
12 | "allowJs": true,
13 | "skipLibCheck": true,
14 | "esModuleInterop": true,
15 | "allowSyntheticDefaultImports": true,
16 | "strict": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "noFallthroughCasesInSwitch": true,
19 | "module": "esnext",
20 | "moduleResolution": "node",
21 | "resolveJsonModule": true,
22 | "isolatedModules": false,
23 | "jsx": "react-jsx",
24 | "plugins": [{ "transform": "typescript-transform-paths", "afterDeclarations": true }],
25 | "experimentalDecorators": true
26 | },
27 | "exclude": ["./node_modules"],
28 | "include": ["src"]
29 | }
30 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpackMerge = require('webpack-merge');
2 | const commonConfig = require('./build/webpack.common.config');
3 | const configs = {
4 | dev: require('./build/webpack.dev.config'),
5 | pro: require('./build/webpack.pro.config'),
6 | debug: require('./build/webpack.debug.config')
7 | };
8 |
9 | const config = (env) => {
10 | if (env.config == 'dev') {
11 | process.env.NODE_ENV = 'development';
12 | process.env.BABEL_ENV = 'development';
13 | } else {
14 | process.env.NODE_ENV = 'production';
15 | process.env.BABEL_ENV = 'production';
16 | process.env.TAILWIND_MODE = 'watch';
17 | }
18 |
19 | if (env.config) {
20 | return webpackMerge.merge(commonConfig(env), configs[env.config]);
21 | }
22 |
23 | return commonConfig(env);
24 | };
25 |
26 | module.exports = config;
27 |
--------------------------------------------------------------------------------