├── .babelrc ├── .buildkite ├── default.nix ├── pipeline.nix └── pipeline.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── .husky └── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── .storybook ├── custom-addons │ ├── index.js │ ├── language-changer-register.jsx │ ├── styles.js │ └── theme-switcher-register.jsx ├── customWebpack.js ├── decorators │ ├── AppDecorator.jsx │ └── storybook.scss ├── main.js ├── manager.js ├── mocks │ ├── electron-log.mock.js │ ├── electron-store.mock.js │ └── electron.mock.js └── preview.js ├── .stylelintrc ├── LICENSE ├── NOTICE ├── README.md ├── bin ├── build-mantis.ts ├── lib │ └── package.ts ├── package-darwin.ts ├── package-linux.ts ├── package-win.ts └── package.ts ├── config-overrides.js ├── config-platform.json5 ├── default.nix ├── image-snapshots ├── dark-address-book--address-book-snap.png ├── dark-address-book--delete-contact-snap.png ├── dark-address-book--edit-contact-snap.png ├── dark-address-book--editable-address-snap.png ├── dark-balance-display--interactive-snap.png ├── dark-balance-display--sync-status-snap.png ├── dark-balance-display--with-zero-balance-snap.png ├── dark-common--copyable-long-text-snap.png ├── dark-common--loading-snap.png ├── dark-common--short-number-snap.png ├── dark-common--status-modal-snap.png ├── dark-common--support-modal-snap.png ├── dark-dialog--interactive-approval-snap.png ├── dark-dialog--interactive-columns-snap.png ├── dark-dialog--interactive-dialog-snap.png ├── dark-dialog--interactive-display-words-snap.png ├── dark-dialog--interactive-dropdown-snap.png ├── dark-dialog--interactive-error-snap.png ├── dark-dialog--interactive-fee-snap.png ├── dark-dialog--interactive-input-snap.png ├── dark-dialog--interactive-message-snap.png ├── dark-dialog--interactive-password-snap.png ├── dark-dialog--interactive-private-key-snap.png ├── dark-dialog--interactive-recovery-phrase-snap.png ├── dark-dialog--interactive-restore-seed-phrase-snap.png ├── dark-dialog--interactive-secrets-snap.png ├── dark-dialog--interactive-show-amount-snap.png ├── dark-dialog--interactive-switch-snap.png ├── dark-dialog--interactive-text-switch-snap.png ├── dark-settings--export-private-key-modal-snap.png ├── dark-settings--settings-snap.png ├── dark-sidebar--remove-wallet-modal-snap.png ├── dark-sidebar--sidebar-snap.png ├── dark-tokens--add-token-modal-snap.png ├── dark-tokens--hide-token-modal-snap.png ├── dark-tokens--receive-token-modal-snap.png ├── dark-tokens--send-token-modal-snap.png ├── dark-tokens--token-list-snap.png ├── dark-tokens--tokens-overview-snap.png ├── dark-transaction-history--confirm-advanced-transaction-snap.png ├── dark-transaction-history--confirm-basic-transaction-snap.png ├── dark-transaction-history--interactive-snap.png ├── dark-transaction-history--receive-transaction-empty-modal-snap.png ├── dark-transaction-history--receive-transaction-snap.png ├── dark-transaction-history--send-advanced-transaction-snap.png ├── dark-transaction-history--send-basic-transaction-snap.png ├── dark-transaction-history--send-transaction-snap.png ├── dark-transaction-history--with-demo-transactions-snap.png ├── dark-transaction-history--with-no-transactions-snap.png ├── dark-wallet-setup--show-path-chooser-snap.png ├── dark-wallet-setup--show-wallet-create-define-step-snap.png ├── dark-wallet-setup--show-wallet-create-display-seed-step-snap.png ├── dark-wallet-setup--show-wallet-create-security-step-snap.png ├── dark-wallet-setup--show-wallet-create-snap.png ├── dark-wallet-setup--show-wallet-create-verify-recovery-step-snap.png ├── dark-wallet-setup--show-wallet-restore-snap.png ├── dark-wallet-setup--terms-and-conditions-snap.png ├── dark-wallet-states--wallet-error-snap.png ├── dark-wallets--no-wallet-snap.png ├── dark-wallets--wallet-list-snap.png ├── light-address-book--address-book-snap.png ├── light-address-book--delete-contact-snap.png ├── light-address-book--edit-contact-snap.png ├── light-address-book--editable-address-snap.png ├── light-balance-display--interactive-snap.png ├── light-balance-display--sync-status-snap.png ├── light-balance-display--with-zero-balance-snap.png ├── light-common--copyable-long-text-snap.png ├── light-common--loading-snap.png ├── light-common--short-number-snap.png ├── light-common--status-modal-snap.png ├── light-common--support-modal-snap.png ├── light-dialog--interactive-approval-snap.png ├── light-dialog--interactive-columns-snap.png ├── light-dialog--interactive-dialog-snap.png ├── light-dialog--interactive-display-words-snap.png ├── light-dialog--interactive-dropdown-snap.png ├── light-dialog--interactive-error-snap.png ├── light-dialog--interactive-fee-snap.png ├── light-dialog--interactive-input-snap.png ├── light-dialog--interactive-message-snap.png ├── light-dialog--interactive-password-snap.png ├── light-dialog--interactive-private-key-snap.png ├── light-dialog--interactive-recovery-phrase-snap.png ├── light-dialog--interactive-restore-seed-phrase-snap.png ├── light-dialog--interactive-secrets-snap.png ├── light-dialog--interactive-show-amount-snap.png ├── light-dialog--interactive-switch-snap.png ├── light-dialog--interactive-text-switch-snap.png ├── light-settings--export-private-key-modal-snap.png ├── light-settings--settings-snap.png ├── light-sidebar--remove-wallet-modal-snap.png ├── light-sidebar--sidebar-snap.png ├── light-tokens--add-token-modal-snap.png ├── light-tokens--hide-token-modal-snap.png ├── light-tokens--receive-token-modal-snap.png ├── light-tokens--send-token-modal-snap.png ├── light-tokens--token-list-snap.png ├── light-tokens--tokens-overview-snap.png ├── light-transaction-history--confirm-advanced-transaction-snap.png ├── light-transaction-history--confirm-basic-transaction-snap.png ├── light-transaction-history--interactive-snap.png ├── light-transaction-history--receive-transaction-empty-modal-snap.png ├── light-transaction-history--receive-transaction-snap.png ├── light-transaction-history--send-advanced-transaction-snap.png ├── light-transaction-history--send-basic-transaction-snap.png ├── light-transaction-history--send-transaction-snap.png ├── light-transaction-history--with-demo-transactions-snap.png ├── light-transaction-history--with-no-transactions-snap.png ├── light-wallet-setup--show-path-chooser-snap.png ├── light-wallet-setup--show-wallet-create-define-step-snap.png ├── light-wallet-setup--show-wallet-create-display-seed-step-snap.png ├── light-wallet-setup--show-wallet-create-security-step-snap.png ├── light-wallet-setup--show-wallet-create-snap.png ├── light-wallet-setup--show-wallet-create-verify-recovery-step-snap.png ├── light-wallet-setup--show-wallet-restore-snap.png ├── light-wallet-setup--terms-and-conditions-snap.png ├── light-wallet-states--wallet-error-snap.png ├── light-wallets--no-wallet-snap.png └── light-wallets--wallet-list-snap.png ├── nix ├── default.nix ├── sources.json └── sources.nix ├── nodemon.json ├── package.json ├── public ├── icon.icns ├── icon.ico ├── icon.png ├── icons │ ├── arrow-down.svg │ ├── chains │ │ └── ethereum.svg │ ├── check-double.svg │ ├── check.svg │ ├── circle.svg │ ├── clock.svg │ ├── copy.svg │ ├── cross.svg │ ├── delete.svg │ ├── edit.svg │ ├── exchange.svg │ ├── hourglass.svg │ ├── loading.svg │ ├── refresh.svg │ ├── speed-high.svg │ ├── speed-low.svg │ ├── speed-medium.svg │ ├── sum.svg │ ├── wallet-restore.svg │ └── wallet.svg ├── index.html └── manifest.json ├── release.nix ├── shell.nix ├── src ├── ApiTest.scss ├── ApiTest.tsx ├── App.scss ├── App.test.tsx ├── App.tsx ├── Settings.scss ├── Settings.stories.tsx ├── Settings.tsx ├── SplashScreen.scss ├── SplashScreen.stories.tsx ├── SplashScreen.tsx ├── __mocks__ │ └── react-inlinesvg.tsx ├── address-book │ ├── Address.scss │ ├── Address.tsx │ ├── AddressBook.scss │ ├── AddressBook.stories.tsx │ ├── AddressBook.test.tsx │ ├── AddressBook.tsx │ ├── DialogAddressSelect.scss │ └── DialogAddressSelect.tsx ├── antd-overrides │ ├── button.scss │ ├── select.scss │ └── switch.scss ├── assets │ ├── bg-base-dark.png │ ├── bg-base-light.png │ ├── bg-moving-dark.png │ ├── bg-moving-light.png │ ├── contracts │ │ └── ERC20.json │ ├── fonts │ │ ├── Montserrat-Bold.ttf │ │ ├── Montserrat-Light.ttf │ │ ├── Montserrat-Medium.ttf │ │ ├── Montserrat-Regular.ttf │ │ └── Montserrat-SemiBold.ttf │ ├── icons │ │ ├── arrow-down.svg │ │ ├── chains │ │ │ └── ethereum.svg │ │ ├── check-double.svg │ │ ├── check.svg │ │ ├── circle.svg │ │ ├── clock.svg │ │ ├── copy.svg │ │ ├── cross.svg │ │ ├── delete.svg │ │ ├── edit.svg │ │ ├── exchange.svg │ │ ├── hourglass.svg │ │ ├── loading.svg │ │ ├── refresh.svg │ │ ├── speed-high.svg │ │ ├── speed-low.svg │ │ ├── speed-medium.svg │ │ ├── sum.svg │ │ ├── wallet-restore.svg │ │ └── wallet.svg │ ├── logo-lockup-dark.svg │ ├── logo-lockup-light.svg │ ├── logo.svg │ ├── logoCrop.svg │ └── wordmark.svg ├── common │ ├── BorderlessInput.scss │ ├── BorderlessInput.tsx │ ├── Common.stories.tsx │ ├── CopyableLongText.scss │ ├── CopyableLongText.tsx │ ├── Dialog.scss │ ├── Dialog.stories.tsx │ ├── Dialog.tsx │ ├── DismissableMessage.scss │ ├── Header.scss │ ├── Header.tsx │ ├── IconButton.scss │ ├── IconButton.tsx │ ├── InfoIcon.tsx │ ├── InlineError.scss │ ├── InlineError.tsx │ ├── Link.scss │ ├── Link.tsx │ ├── Loading.scss │ ├── Loading.tsx │ ├── MantisModal.scss │ ├── MantisModal.tsx │ ├── ShortNumber.tsx │ ├── StatusModal.scss │ ├── StatusModal.tsx │ ├── SupportModal.scss │ ├── SupportModal.tsx │ ├── SyncStatus.scss │ ├── SyncStatus.tsx │ ├── Trans.tsx │ ├── backend-state.ts │ ├── chains.ts │ ├── clipboard.ts │ ├── constants │ │ └── ui.ts │ ├── dialog │ │ ├── DialogApproval.scss │ │ ├── DialogApproval.tsx │ │ ├── DialogColumns.scss │ │ ├── DialogColumns.tsx │ │ ├── DialogDisplayWords.scss │ │ ├── DialogDisplayWords.tsx │ │ ├── DialogDropdown.scss │ │ ├── DialogDropdown.tsx │ │ ├── DialogError.scss │ │ ├── DialogError.tsx │ │ ├── DialogFee.scss │ │ ├── DialogFee.tsx │ │ ├── DialogInput.scss │ │ ├── DialogInput.tsx │ │ ├── DialogMessage.scss │ │ ├── DialogMessage.tsx │ │ ├── DialogPassword.scss │ │ ├── DialogPassword.test.ts │ │ ├── DialogPassword.tsx │ │ ├── DialogQRCode.scss │ │ ├── DialogQRCode.tsx │ │ ├── DialogRecoveryPhrase.scss │ │ ├── DialogRecoveryPhrase.tsx │ │ ├── DialogSecrets.scss │ │ ├── DialogSecrets.tsx │ │ ├── DialogSeedPhrase.scss │ │ ├── DialogSeedPhrase.tsx │ │ ├── DialogShowAmount.scss │ │ ├── DialogShowAmount.tsx │ │ ├── DialogSwitch.scss │ │ ├── DialogSwitch.tsx │ │ ├── DialogTextSwitch.scss │ │ └── DialogTextSwitch.tsx │ ├── dismissable-message.tsx │ ├── formatters.test.ts │ ├── formatters.ts │ ├── hook-utils.ts │ ├── i18n-pseudo.ts │ ├── i18n.ts │ ├── io-helpers.test.ts │ ├── io-helpers.ts │ ├── ipc-util.ts │ ├── logger.ts │ ├── mnemonic.ts │ ├── notify.ts │ ├── store.ts │ ├── test-helpers.tsx │ ├── units.ts │ ├── util.test.ts │ ├── util.ts │ ├── wallet-state.test.ts │ ├── wallet-state.ts │ └── wallet-status-guard.tsx ├── config │ ├── __mocks__ │ │ └── renderer.ts │ ├── main.ts │ ├── renderer.ts │ └── type.ts ├── declarations.d.ts ├── external-link-config.ts ├── functions.scss ├── index.scss ├── index.tsx ├── jest.config.ts ├── layout │ ├── Router.scss │ ├── Router.tsx │ ├── Sidebar.scss │ ├── Sidebar.stories.tsx │ └── Sidebar.tsx ├── main │ ├── MantisProcess.ts │ ├── data-dir.ts │ ├── i18n.ts │ ├── log-exporter.ts │ ├── logger.ts │ ├── main.ts │ ├── menu.ts │ ├── port-usage-check.ts │ ├── status.ts │ ├── store.ts │ ├── streamUtils.ts │ ├── test │ │ ├── data │ │ │ ├── generate.sh │ │ │ ├── mantisCA.p12 │ │ │ ├── mantisCA.pem │ │ │ ├── params.json │ │ │ └── password │ │ └── tls.test.ts │ ├── tls.ts │ ├── types.d.ts │ └── util.ts ├── partial.scss ├── react-app-env.d.ts ├── router-state.ts ├── routes-config.ts ├── sass-includes.scss ├── settings-state.tsx ├── setupTests.ts ├── shared │ ├── ArrayOps.ts │ ├── OptionOps.ts │ ├── RxOps.ts │ ├── SetOps.test.ts │ ├── SetOps.ts │ ├── extendable-error.ts │ ├── i18n.ts │ ├── index.ts │ ├── ipc-types.ts │ ├── typeUtils.ts │ ├── utils.ts │ └── version.ts ├── storybook-util │ ├── backend-state-decorator.tsx │ ├── custom-knobs.ts │ ├── dummies.ts │ ├── essential-decorators.ts │ ├── full-screen-decorator.tsx │ ├── router-state-decorator.tsx │ ├── settings-state-decorator.tsx │ ├── shared-constants.ts │ ├── tokens-state-decorator.tsx │ └── wallet-state-decorator.tsx ├── storyshots.test.ts ├── themes.scss ├── themify.scss ├── tokens │ ├── TokenList.scss │ ├── TokenList.tsx │ ├── Tokens.scss │ ├── Tokens.stories.tsx │ ├── Tokens.tsx │ ├── modals │ │ ├── AddTokenModal.tsx │ │ ├── HideTokenModal.tsx │ │ ├── ReceiveTokenModal.scss │ │ ├── ReceiveTokenModal.tsx │ │ └── SendTokenModal.tsx │ ├── tokens-state.tsx │ └── tokens-utils.ts ├── translations │ └── en │ │ ├── main.json │ │ └── renderer.json ├── types │ ├── tests │ │ ├── index.d.ts │ │ ├── tsconfig.json │ │ ├── tslint.json │ │ └── typeUtils.test-d.ts │ └── web3-eth.d.ts ├── vars-for-ts.scss ├── vars.scss ├── wallets │ ├── BalanceDisplay.scss │ ├── BalanceDisplay.stories.tsx │ ├── BalanceDisplay.test.tsx │ ├── BalanceDisplay.tsx │ ├── NoWallet.scss │ ├── NoWallet.tsx │ ├── TermsAndConditions.scss │ ├── TermsAndConditions.tsx │ ├── TermsAndConditionsStep.tsx │ ├── TransactionHistory.scss │ ├── TransactionHistory.stories.tsx │ ├── TransactionHistory.test.tsx │ ├── TransactionHistory.tsx │ ├── TransactionList.scss │ ├── TransactionList.tsx │ ├── TransactionRow.scss │ ├── TransactionRow.tsx │ ├── Wallet.scss │ ├── Wallet.tsx │ ├── WalletActionBox.scss │ ├── WalletActionBox.tsx │ ├── WalletCreate.test.tsx │ ├── WalletCreate.tsx │ ├── WalletListSidebar.scss │ ├── WalletListSidebar.tsx │ ├── WalletPathChooser.scss │ ├── WalletPathChooser.tsx │ ├── WalletRestore.test.tsx │ ├── WalletRestore.tsx │ ├── WalletSetup.scss │ ├── WalletSetup.stories.tsx │ ├── WalletSetup.tsx │ ├── Wallets.stories.tsx │ ├── Wallets.tsx │ ├── create │ │ ├── WalletCreateDefineStep.tsx │ │ ├── WalletCreateDisplayRecoveryStep.tsx │ │ ├── WalletCreateSecurityStep.tsx │ │ └── WalletCreateVerifyRecoveryStep.tsx │ ├── history │ │ ├── BatchRange.test.ts │ │ ├── BatchRange.ts │ │ ├── HistoryStore.ts │ │ ├── StoredHistory.ts │ │ ├── Transaction.ts │ │ ├── TransactionHistory.test.ts │ │ ├── TransactionHistory.ts │ │ ├── TransactionHistoryService.test.ts │ │ ├── TransactionHistoryService.ts │ │ ├── index.ts │ │ └── test │ │ │ └── historyTestUtils.ts │ ├── modals │ │ ├── ChangeNetwork.tsx │ │ ├── ExportPrivateKey.scss │ │ ├── ExportPrivateKey.tsx │ │ ├── ReceiveTransaction.scss │ │ ├── ReceiveTransaction.tsx │ │ ├── RemoveWalletModal.tsx │ │ └── SendTransaction.tsx │ └── sendTransaction │ │ ├── ConfirmAdvancedTransaction.tsx │ │ ├── ConfirmBasicTransaction.tsx │ │ ├── SendAdvancedTransaction.tsx │ │ ├── SendBasicTransaction.tsx │ │ ├── common.ts │ │ └── index.ts └── web3.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.main.json ├── useful-links.md ├── webpack.main.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-optional-chaining", 4 | "@babel/plugin-transform-react-jsx" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.buildkite/default.nix: -------------------------------------------------------------------------------- 1 | import (import ../nix/sources.nix).nixkite 2 | -------------------------------------------------------------------------------- /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - label: ":nix::point_right::pipeline:" 3 | command: | 4 | export NIX_PATH="nixpkgs=$(nix eval --impure --raw --expr '(import nix/sources.nix).nixpkgs')" 5 | nix eval --impure --json --expr '(import ./.buildkite { pipeline = ./.buildkite/pipeline.nix; })' \ 6 | | buildkite-agent pipeline upload --no-interpolation 7 | 8 | agents: 9 | queue: project42 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.{js,ts,jsx,tsx,py,css,scss}] 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | build 4 | dist 5 | storybook-static 6 | docs 7 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | # Describe the bug 8 | A clear and concise description of what the bug is. 9 | 10 | # To Reproduce 11 | Steps to reproduce the behavior: 12 | 13 | 1. Go to '...' 14 | 2. Click on '....' 15 | 3. Scroll down to '....' 16 | 4. See error 17 | 18 | # Expected behavior 19 | A clear and concise description of what you expected to happen. 20 | 21 | # Desktop (please complete the following information): 22 | 23 | - OS: [e.g. Ubuntu 4.15.0-20-generic] 24 | - Version: [e.g v0.1.0] 25 | - Configuration: [e.g Mainnet on default configuration] 26 | 27 | # Additional context 28 | Add any other context about the problem here, include screenshots, logs, provide links, propose solution, etc. 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | _A clear and concise description of what this pull request does or fixes._ 4 | 5 | # Proposed Solution 6 | 7 | _**Optional** Explain how does this PR solves the problem stated in [Description](#Description). You can also enumerate different alternatives considered while approaching this task._ 8 | 9 | # Important Changes Introduced 10 | 11 | _**Optional** Notice Reviewers about changes that were introduced while developing this task_ 12 | 13 | # Testing 14 | 15 | _**Optional** Leave some recommendations should be useful while reviewers are testing this PR_ 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE config 2 | .vscode 3 | .idea 4 | 5 | # local 6 | config.json5 7 | 8 | # dependencies 9 | /node_modules 10 | /.pnp 11 | .pnp.js 12 | .yalc 13 | yalc.lock 14 | 15 | # testing 16 | /coverage 17 | 18 | # production 19 | /build 20 | /storybook-static 21 | /dist 22 | 23 | # snapshot-diffs 24 | image-snapshots/__diff_output__ 25 | 26 | # misc 27 | .DS_Store 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | npm-debug.log* 34 | yarn-debug.log* 35 | yarn-error.log* 36 | 37 | .envrc 38 | .python-version 39 | 40 | # nix 41 | result 42 | result-* 43 | 44 | # electron-builder 45 | /.log 46 | 47 | # FIXME 48 | LOGSFILENAME_IS_UNDEFINED.log 49 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mantis"] 2 | path = mantis 3 | url = git@github.com:input-output-hk/mantis.git 4 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/fermium 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # project specific 2 | docs 3 | build 4 | coverage 5 | dist 6 | storybook-static 7 | mantis 8 | .* 9 | 10 | # general 11 | *.json 12 | 13 | # whitelist 14 | !/.eslintrc.js 15 | !/.prettierrc.js 16 | !.storybook 17 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | bracketSpacing: false, 4 | endOfLine: 'lf', 5 | parser: 'typescript', 6 | printWidth: 100, 7 | quoteProps: 'consistent', 8 | semi: false, 9 | singleQuote: true, 10 | trailingComma: 'all', 11 | overrides: [ 12 | { 13 | files: '*.scss', 14 | options: {parser: 'scss'}, 15 | }, 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /.storybook/custom-addons/index.js: -------------------------------------------------------------------------------- 1 | export {registerLanguageChanger} from './language-changer-register' 2 | export {registerThemeSwitcher} from './theme-switcher-register' 3 | -------------------------------------------------------------------------------- /.storybook/custom-addons/language-changer-register.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import {addons, types} from '@storybook/addons' 3 | import { 4 | LANGUAGE_CHANGER_ADDON_ID, 5 | LANGUAGE_CHANGER_PSEUDO_SWITCH, 6 | } from '../../src/storybook-util/shared-constants.ts' 7 | import {lightButtonStyle} from './styles' 8 | 9 | const LanguageChanger = ({ api }) => { 10 | const [isPseudoLanguageUsed, usePseudoLanguage] = useState(false) 11 | 12 | const handleClick = (isPseudo) => () => { 13 | usePseudoLanguage(isPseudo) 14 | api.emit(LANGUAGE_CHANGER_PSEUDO_SWITCH, isPseudo) 15 | } 16 | 17 | return ( 18 | 19 | Switch to {isPseudoLanguageUsed ? 'Normal Language' : 'Pseudo Language'} 20 | 21 | ) 22 | } 23 | 24 | export const registerLanguageChanger = () => addons.register(LANGUAGE_CHANGER_ADDON_ID, (api) => { 25 | addons.add(LANGUAGE_CHANGER_PSEUDO_SWITCH, { 26 | title: 'Language', 27 | type: types.TOOL, 28 | match: ({viewMode}) => viewMode === 'story', 29 | render: () => , 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /.storybook/custom-addons/styles.js: -------------------------------------------------------------------------------- 1 | const buttonStyle = { 2 | display: 'block', 3 | margin: '5px', 4 | padding: '5px 10px', 5 | outline: 'none', 6 | verticalAlign: 'middle', 7 | fontSize: '14px', 8 | fontWeight: 'bold', 9 | lineHeight: '20px', 10 | userSelect: 'none', 11 | cursor: 'pointer', 12 | } 13 | 14 | export const lightButtonStyle = { 15 | color: '#000', 16 | backgroundColor: '#f7f7f7', 17 | ...buttonStyle, 18 | } 19 | 20 | export const darkButtonStyle = { 21 | color: '#f7f7f7', 22 | backgroundColor: '#000', 23 | ...buttonStyle, 24 | } 25 | -------------------------------------------------------------------------------- /.storybook/custom-addons/theme-switcher-register.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import {addons, types} from '@storybook/addons' 3 | import { 4 | THEME_SWITCHER_ADDON_ID, 5 | THEME_SWITCHER_CHANGE, 6 | } from '../../src/storybook-util/shared-constants.ts' 7 | import {darkButtonStyle, lightButtonStyle} from './styles' 8 | 9 | const ThemeSwitch = ({api}) => { 10 | const [theme, setTheme] = useState('dark') 11 | 12 | const handleClick = (theme) => () => { 13 | setTheme(theme) 14 | api.emit(THEME_SWITCHER_CHANGE, theme) 15 | } 16 | 17 | return ( 18 | <> 19 | {theme === 'light' && ( 20 | 21 | Switch to Dark Theme 22 | 23 | )} 24 | {theme === 'dark' && ( 25 | 26 | Switch to Light Theme 27 | 28 | )} 29 | 30 | ) 31 | } 32 | 33 | export const registerThemeSwitcher = () => addons.register(THEME_SWITCHER_ADDON_ID, (api) => { 34 | addons.add(THEME_SWITCHER_ADDON_ID, { 35 | title: 'Themes', 36 | type: types.TOOL, 37 | match: ({viewMode}) => viewMode === 'story', 38 | render: () => , 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /.storybook/customWebpack.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const mainOverride = require('../config-overrides.js') 4 | 5 | module.exports = { 6 | overrideConfig: (config) => { 7 | config = mainOverride(config) 8 | 9 | config.plugins.push( 10 | new webpack.NormalModuleReplacementPlugin( 11 | /electron$/, 12 | path.resolve(__dirname, './mocks/electron.mock.js'), 13 | ), 14 | ) 15 | config.plugins.push( 16 | new webpack.NormalModuleReplacementPlugin( 17 | /electron-store$/, 18 | path.resolve(__dirname, './mocks/electron-store.mock.js'), 19 | ), 20 | ) 21 | config.plugins.push( 22 | new webpack.NormalModuleReplacementPlugin( 23 | /electron-log$/, 24 | path.resolve(__dirname, './mocks/electron-log.mock.js'), 25 | ), 26 | ) 27 | 28 | // output.globalObject is set to "window". It must be set to "self" to support HMR in Workers 29 | config.output.globalObject = 'self' 30 | 31 | return config 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /.storybook/decorators/AppDecorator.jsx: -------------------------------------------------------------------------------- 1 | import url from 'url' 2 | import React, {useEffect} from 'react' 3 | 4 | import '../../src/index.scss' 5 | import '../../src/App.scss' 6 | // At the end intentionally, so we can test if antd overrides work properly: 7 | import 'antd/dist/antd.less' 8 | // Custom story overrides, e.g. disable antimations 9 | import './storybook.scss' 10 | 11 | export const AppDecorator = (storyFn) => { 12 | useEffect(() => { 13 | // set Mantis body class for antd overwrites 14 | document.body.id = 'Mantis' 15 | 16 | const currentURL = url.parse(window.location.href, true) 17 | if (currentURL.query['disable-animations'] === 'true') { 18 | document.body.classList.add('disable-animations') 19 | } 20 | }, []) 21 | 22 | return ( 23 |
24 |
{storyFn()}
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /.storybook/decorators/storybook.scss: -------------------------------------------------------------------------------- 1 | .disable-animations #root { 2 | *, 3 | ::before, 4 | ::after { 5 | animation: none !important; 6 | } 7 | } 8 | 9 | main { 10 | position: relative; 11 | height: 100vh; 12 | padding: 50px; 13 | } 14 | 15 | .storybook-app { 16 | display: grid; 17 | grid-template-columns: 1fr; 18 | grid-template-rows: 1fr; 19 | min-height: 100vh; 20 | overflow: auto; 21 | } 22 | 23 | .ant-message { 24 | padding-left: 0 !important; 25 | } 26 | 27 | .MantisModal { 28 | margin-left: 0 !important; 29 | } 30 | 31 | .no-padding { 32 | padding: 0 !important; 33 | } 34 | 35 | .WalletError { 36 | .retry-countdown { 37 | display: none; 38 | } 39 | } 40 | 41 | #App { 42 | display: block; 43 | background-image: none !important; 44 | } 45 | 46 | div#root { 47 | background-image: none !important; 48 | } 49 | 50 | .theme-dark div#root { 51 | background-color: #131313; 52 | } 53 | 54 | .theme-light div#root { 55 | background-color: #15ffd7; 56 | } 57 | 58 | .flex-content { 59 | height: 80vh !important; 60 | } 61 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const {overrideConfig} = require('./customWebpack.js') 2 | 3 | module.exports = { 4 | stories: ['../src/**/*.stories.tsx'], 5 | addons: [ 6 | '@storybook/preset-create-react-app', 7 | '@storybook/addon-knobs/register', 8 | '@storybook/addon-actions/register', 9 | '@storybook/addon-links/register', 10 | ], 11 | webpackFinal: (config) => { 12 | const newConfig = overrideConfig(config) 13 | return {...config, module: newConfig.module} 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import {addons} from '@storybook/addons' 2 | import {create} from '@storybook/theming' 3 | import {registerLanguageChanger, registerThemeSwitcher} from './custom-addons' 4 | import logo from '../src/assets/logo.svg' 5 | 6 | registerLanguageChanger() 7 | registerThemeSwitcher() 8 | 9 | addons.setConfig({ 10 | theme: create({ 11 | base: 'light', 12 | brandTitle: 'Mantis Stories', 13 | brandImage: logo, 14 | }), 15 | showPanel: true, 16 | showNav: true, 17 | isToolshown: true, 18 | sidebarAnimations: false, 19 | }) 20 | -------------------------------------------------------------------------------- /.storybook/mocks/electron-log.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | create: () => ({ 3 | error: console.error, 4 | info: console.info, 5 | debug: console.debug, 6 | log: console.log, 7 | warn: console.warn, 8 | transports: {file: {}}, 9 | }), 10 | } 11 | -------------------------------------------------------------------------------- /.storybook/mocks/electron-store.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /.storybook/mocks/electron.mock.js: -------------------------------------------------------------------------------- 1 | const mockedGlobal = { 2 | mantisWalletConfig: { 3 | rpcAddress: 'localhost:1234', 4 | mantis: { 5 | dataDir: null, 6 | }, 7 | }, 8 | mantisWalletStatus: { 9 | fetchParams: { 10 | status: 'notRunning', 11 | }, 12 | wallet: { 13 | status: 'notRunning', 14 | }, 15 | node: { 16 | status: 'notRunning', 17 | }, 18 | info: { 19 | platform: 'Linux', 20 | platformVersion: '10.0.0', 21 | cpu: 'Intel CPU', 22 | memory: 12345678, 23 | 24 | mantisWalletVersion: 'v0.11.0', 25 | mainPid: 123, 26 | }, 27 | }, 28 | } 29 | 30 | module.exports = { 31 | remote: { 32 | getGlobal: (name) => mockedGlobal[name], 33 | }, 34 | ipcRenderer: { 35 | on: () => undefined, 36 | removeAllListeners: () => undefined, 37 | send: () => undefined, 38 | }, 39 | shell: { 40 | openExternal: () => undefined, 41 | }, 42 | } 43 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import {AppDecorator} from './decorators/AppDecorator' 2 | 3 | export const decorators = [AppDecorator] 4 | export const parameters = {layout: 'fullscreen'} 5 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-recommended", 4 | "stylelint-config-recommended-scss", 5 | "stylelint-config-property-sort-order-smacss", 6 | "stylelint-config-css-modules", 7 | ], 8 | "rules": { 9 | "declaration-block-no-duplicate-properties": true, 10 | "at-rule-no-unknown": null, 11 | "scss/at-rule-no-unknown": true, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Input Output (Hong Kong) Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /bin/lib/package.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import {promises as fs} from 'fs' 3 | import * as electronPackager from 'electron-packager' 4 | import packager from 'electron-packager' 5 | 6 | export const packageMantisWallet = ( 7 | options: Partial, 8 | ): Promise => { 9 | const appDir = path.resolve(__dirname, '..', '..') 10 | return packager({ 11 | dir: appDir, 12 | arch: 'x64', 13 | electronZipDir: process.env['ELECTRON_ZIP_DIR'], 14 | extraResource: process.env['MANTIS_DIST_DIR'] || path.resolve(appDir, '..', 'mantis-dist'), 15 | // The icon's extension is normalized inside electron-packager per platform 16 | // for windows it will use icon.ico, for mac it will use icon.icns, 17 | // but only if you leave out the file extension. 18 | // For Linux: you should use BrowserWindow({icon}) to set it. It should be a png. 19 | icon: path.resolve(appDir, 'public/icon'), 20 | ignore: /^\/(?!build).*/, 21 | out: path.resolve(appDir, 'dist'), 22 | afterCopy: [ 23 | (buildPath, _electronVersion, _platform, _arch, callback) => 24 | fs 25 | .rename( 26 | path.resolve(buildPath, 'build/main', 'package.json'), 27 | path.resolve(buildPath, 'package.json'), 28 | ) 29 | .then(callback), 30 | ], 31 | overwrite: true, 32 | ...options, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /bin/package-darwin.ts: -------------------------------------------------------------------------------- 1 | import {TargetPlatform} from 'electron-packager' 2 | import {packageMantisWallet} from './lib/package' 3 | 4 | const platforms: TargetPlatform[] = ['darwin'] 5 | 6 | platforms.map((platform) => packageMantisWallet({platform})) 7 | -------------------------------------------------------------------------------- /bin/package-linux.ts: -------------------------------------------------------------------------------- 1 | import {TargetPlatform} from 'electron-packager' 2 | import {packageMantisWallet} from './lib/package' 3 | 4 | const platforms: TargetPlatform[] = ['linux'] 5 | 6 | platforms.map((platform) => packageMantisWallet({platform})) 7 | -------------------------------------------------------------------------------- /bin/package-win.ts: -------------------------------------------------------------------------------- 1 | import {packageMantisWallet} from './lib/package' 2 | 3 | packageMantisWallet({platform: 'win32'}) 4 | -------------------------------------------------------------------------------- /bin/package.ts: -------------------------------------------------------------------------------- 1 | import {TargetPlatform} from 'electron-packager' 2 | import {packageMantisWallet} from './lib/package' 3 | 4 | const platforms: TargetPlatform[] = ['darwin', 'linux'] 5 | 6 | platforms.map((platform) => packageMantisWallet({platform})) 7 | -------------------------------------------------------------------------------- /config-platform.json5: -------------------------------------------------------------------------------- 1 | { 2 | // Development configuration overrides 3 | "openDevTools": true, 4 | "walletDataDir": "~/.mantis-wallet-dev", 5 | } 6 | -------------------------------------------------------------------------------- /image-snapshots/dark-address-book--address-book-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-address-book--address-book-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-address-book--delete-contact-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-address-book--delete-contact-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-address-book--edit-contact-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-address-book--edit-contact-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-address-book--editable-address-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-address-book--editable-address-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-balance-display--interactive-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-balance-display--interactive-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-balance-display--sync-status-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-balance-display--sync-status-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-balance-display--with-zero-balance-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-balance-display--with-zero-balance-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-common--copyable-long-text-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-common--copyable-long-text-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-common--loading-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-common--loading-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-common--short-number-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-common--short-number-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-common--status-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-common--status-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-common--support-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-common--support-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-approval-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-approval-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-columns-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-columns-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-dialog-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-dialog-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-display-words-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-display-words-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-dropdown-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-dropdown-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-error-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-error-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-fee-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-fee-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-input-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-input-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-message-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-message-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-password-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-password-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-private-key-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-private-key-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-recovery-phrase-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-recovery-phrase-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-restore-seed-phrase-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-restore-seed-phrase-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-secrets-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-secrets-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-show-amount-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-show-amount-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-switch-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-switch-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-dialog--interactive-text-switch-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-dialog--interactive-text-switch-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-settings--export-private-key-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-settings--export-private-key-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-settings--settings-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-settings--settings-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-sidebar--remove-wallet-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-sidebar--remove-wallet-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-sidebar--sidebar-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-sidebar--sidebar-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-tokens--add-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-tokens--add-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-tokens--hide-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-tokens--hide-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-tokens--receive-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-tokens--receive-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-tokens--send-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-tokens--send-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-tokens--token-list-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-tokens--token-list-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-tokens--tokens-overview-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-tokens--tokens-overview-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--confirm-advanced-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--confirm-advanced-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--confirm-basic-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--confirm-basic-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--interactive-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--interactive-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--receive-transaction-empty-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--receive-transaction-empty-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--receive-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--receive-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--send-advanced-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--send-advanced-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--send-basic-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--send-basic-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--send-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--send-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--with-demo-transactions-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--with-demo-transactions-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-transaction-history--with-no-transactions-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-transaction-history--with-no-transactions-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--show-path-chooser-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--show-path-chooser-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--show-wallet-create-define-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--show-wallet-create-define-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--show-wallet-create-display-seed-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--show-wallet-create-display-seed-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--show-wallet-create-security-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--show-wallet-create-security-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--show-wallet-create-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--show-wallet-create-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--show-wallet-create-verify-recovery-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--show-wallet-create-verify-recovery-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--show-wallet-restore-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--show-wallet-restore-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-setup--terms-and-conditions-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-setup--terms-and-conditions-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallet-states--wallet-error-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallet-states--wallet-error-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallets--no-wallet-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallets--no-wallet-snap.png -------------------------------------------------------------------------------- /image-snapshots/dark-wallets--wallet-list-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/dark-wallets--wallet-list-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-address-book--address-book-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-address-book--address-book-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-address-book--delete-contact-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-address-book--delete-contact-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-address-book--edit-contact-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-address-book--edit-contact-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-address-book--editable-address-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-address-book--editable-address-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-balance-display--interactive-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-balance-display--interactive-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-balance-display--sync-status-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-balance-display--sync-status-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-balance-display--with-zero-balance-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-balance-display--with-zero-balance-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-common--copyable-long-text-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-common--copyable-long-text-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-common--loading-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-common--loading-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-common--short-number-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-common--short-number-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-common--status-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-common--status-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-common--support-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-common--support-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-approval-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-approval-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-columns-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-columns-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-dialog-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-dialog-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-display-words-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-display-words-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-dropdown-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-dropdown-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-error-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-error-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-fee-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-fee-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-input-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-input-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-message-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-message-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-password-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-password-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-private-key-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-private-key-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-recovery-phrase-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-recovery-phrase-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-restore-seed-phrase-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-restore-seed-phrase-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-secrets-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-secrets-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-show-amount-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-show-amount-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-switch-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-switch-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-dialog--interactive-text-switch-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-dialog--interactive-text-switch-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-settings--export-private-key-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-settings--export-private-key-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-settings--settings-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-settings--settings-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-sidebar--remove-wallet-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-sidebar--remove-wallet-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-sidebar--sidebar-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-sidebar--sidebar-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-tokens--add-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-tokens--add-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-tokens--hide-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-tokens--hide-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-tokens--receive-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-tokens--receive-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-tokens--send-token-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-tokens--send-token-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-tokens--token-list-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-tokens--token-list-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-tokens--tokens-overview-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-tokens--tokens-overview-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--confirm-advanced-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--confirm-advanced-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--confirm-basic-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--confirm-basic-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--interactive-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--interactive-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--receive-transaction-empty-modal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--receive-transaction-empty-modal-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--receive-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--receive-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--send-advanced-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--send-advanced-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--send-basic-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--send-basic-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--send-transaction-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--send-transaction-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--with-demo-transactions-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--with-demo-transactions-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-transaction-history--with-no-transactions-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-transaction-history--with-no-transactions-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--show-path-chooser-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--show-path-chooser-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--show-wallet-create-define-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--show-wallet-create-define-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--show-wallet-create-display-seed-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--show-wallet-create-display-seed-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--show-wallet-create-security-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--show-wallet-create-security-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--show-wallet-create-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--show-wallet-create-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--show-wallet-create-verify-recovery-step-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--show-wallet-create-verify-recovery-step-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--show-wallet-restore-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--show-wallet-restore-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-setup--terms-and-conditions-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-setup--terms-and-conditions-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallet-states--wallet-error-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallet-states--wallet-error-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallets--no-wallet-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallets--no-wallet-snap.png -------------------------------------------------------------------------------- /image-snapshots/light-wallets--wallet-list-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/image-snapshots/light-wallets--wallet-list-snap.png -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem }: 2 | let 3 | sources = import ./sources.nix; 4 | inherit (sources) nixpkgs gitignore yarn2nix; 5 | 6 | haskellNix = import sources."haskell.nix" { 7 | sourceOverrides = sources; 8 | inherit system; 9 | }; 10 | 11 | pkgs = import nixpkgs { 12 | inherit system; 13 | config = { }; 14 | overlays = [ ]; 15 | }; 16 | inherit (import gitignore { inherit (pkgs) lib; }) gitignoreSource; 17 | 18 | nodejs = pkgs.nodejs-14_x; 19 | yarn = pkgs.yarn.override { inherit nodejs; }; 20 | 21 | in 22 | { 23 | inherit pkgs nodejs yarn; 24 | 25 | mkSrc = import sources.nix-mksrc { inherit (pkgs) lib; }; 26 | 27 | electron-zip = 28 | let 29 | 30 | electron = { 31 | x86_64-linux = sources.electron-linux; 32 | x86_64-darwin = sources.electron-darwin; 33 | }; 34 | 35 | in 36 | pkgs.runCommand "electron-zip" 37 | { } '' 38 | mkdir $out 39 | cp ${electron.${system}} $out/$(stripHash ${electron.${system}}) 40 | ''; 41 | 42 | nodeHeaders = pkgs.fetchurl { 43 | url = 44 | "https://nodejs.org/download/release/v${nodejs.version}/node-v${nodejs.version}-headers.tar.gz"; 45 | hash = "sha256:0zwl2d32m6cdi3cm80szba1x2nmn6hzcbjmhi4iipgmnc4jp6pl1"; 46 | }; 47 | 48 | yarn2nix = import yarn2nix rec { inherit pkgs nodejs yarn; }; 49 | 50 | inherit (haskellNix.pkgs.haskell-nix.haskellLib) cleanGit cleanSourceWith; 51 | } 52 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "restartable": false, 3 | "watch": ["build/main/main.js"], 4 | "signal": "SIGINT", 5 | "delay": 3000 6 | } 7 | -------------------------------------------------------------------------------- /public/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/public/icon.icns -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/public/icon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/public/icon.png -------------------------------------------------------------------------------- /public/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/chains/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 18 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /public/icons/check-double.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /public/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/icons/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/icons/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/icons/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/icons/exchange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/hourglass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/icons/sum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mantis Wallet", 3 | "name": "Mantis Wallet", 4 | "icons": [ 5 | { 6 | "src": "icon.ico", 7 | "type": "image/x-icon", 8 | "sizes": "256x256" 9 | }, 10 | { 11 | "src": "icon.png", 12 | "type": "image/png", 13 | "sizes": "1024x1024" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /release.nix: -------------------------------------------------------------------------------- 1 | { src ? builtins.fetchGit { url = ./.; submodules = true; } 2 | , supportedSystems ? [ builtins.currentSystem ] 3 | }: 4 | let 5 | sources = import nix/sources.nix; 6 | lib = import (sources.nixpkgs + "/lib"); 7 | in 8 | { 9 | mantisWallet = lib.genAttrs supportedSystems (system: import src { 10 | inherit src system; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | with import ./nix { }; 2 | 3 | pkgs.mkShell { 4 | CI = __getEnv "CI"; 5 | buildInputs = [ yarn nodejs pkgs.git pkgs.openssh pkgs.python ] 6 | ++ (pkgs.lib.optional pkgs.stdenv.isLinux pkgs.wineWowPackages.base); 7 | } 8 | -------------------------------------------------------------------------------- /src/ApiTest.scss: -------------------------------------------------------------------------------- 1 | @import './partial', './vars'; 2 | 3 | .ApiTest { 4 | .input { 5 | margin-bottom: $dialog-component-spacing; 6 | 7 | label, 8 | div.label { 9 | @extend %dialog-label; 10 | display: block; 11 | } 12 | 13 | input { 14 | height: $dialog-component-height; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ApiTest.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Switch} from 'antd' 3 | import {SettingsState} from './settings-state' 4 | import './ApiTest.scss' 5 | 6 | export const ApiTest = (): JSX.Element => { 7 | const settingState = SettingsState.useContainer() 8 | 9 | return ( 10 |
11 |
12 | Api Test Interface 13 |
14 | 15 |
16 |

Settings

17 |
18 |
Pseudo language
19 | 24 |
25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import './themify', './vars'; 2 | @import './antd-overrides/switch'; 3 | @import './antd-overrides/select'; 4 | @import './antd-overrides/button'; 5 | 6 | #App { 7 | font-weight: 500; 8 | 9 | @include themify($themes) { 10 | color: themed('text-color'); 11 | } 12 | 13 | .loaded { 14 | display: grid; 15 | grid-template-columns: auto 1fr; 16 | overflow: hidden; 17 | transition: background-position $bg-transition-duration; 18 | background-repeat: no-repeat; 19 | background-position: 100% bottom; 20 | background-size: contain; 21 | 22 | &.txns { 23 | background-position: 125% bottom; 24 | } 25 | 26 | &.address_book { 27 | background-position: 135% bottom; 28 | } 29 | 30 | &.settings { 31 | background-position: 145% bottom; 32 | } 33 | 34 | @include themify($themes) { 35 | background-image: themed('bg-moving'); 36 | color: themed('text-color'); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | jest.mock('electron-store') 6 | jest.mock('./web3') 7 | 8 | it('renders without crashing', () => { 9 | const div = document.createElement('div') 10 | ReactDOM.render(, div) 11 | ReactDOM.unmountComponentAtNode(div) 12 | }) 13 | -------------------------------------------------------------------------------- /src/Settings.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {action} from '@storybook/addon-actions' 3 | import {ESSENTIAL_DECORATORS} from './storybook-util/essential-decorators' 4 | import {Settings} from './Settings' 5 | import {ExportPrivateKeyModal} from './wallets/modals/ExportPrivateKey' 6 | 7 | export default { 8 | title: 'Settings', 9 | decorators: ESSENTIAL_DECORATORS, 10 | } 11 | 12 | export const settings = (): JSX.Element => 13 | 14 | export const exportPrivateKeyModal = (): JSX.Element => ( 15 | 'example-private-key'} 17 | onCancel={action('onCancel')} 18 | visible 19 | /> 20 | ) 21 | -------------------------------------------------------------------------------- /src/SplashScreen.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {SplashScreen} from './SplashScreen' 3 | import {toFullScreen} from './storybook-util/full-screen-decorator' 4 | import {ESSENTIAL_DECORATORS} from './storybook-util/essential-decorators' 5 | 6 | export default { 7 | title: 'Splash Screen', 8 | decorators: [...ESSENTIAL_DECORATORS, toFullScreen], 9 | } 10 | 11 | export const splashScreen = (): JSX.Element => 12 | -------------------------------------------------------------------------------- /src/__mocks__/react-inlinesvg.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function InlineSVG(props: {title: string}): JSX.Element { 4 | return ( 5 | 6 | {props.title} 7 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/address-book/Address.scss: -------------------------------------------------------------------------------- 1 | @import '../vars'; 2 | 3 | .AddressEditor { 4 | max-width: 180px; 5 | margin-bottom: 1px; 6 | 7 | &.ant-input-affix-wrapper { 8 | padding: 0 5px; 9 | font-size: $font-size-s; 10 | } 11 | 12 | .ant-input { 13 | font-size: $font-size-s; 14 | } 15 | } 16 | 17 | .Address { 18 | display: inline-block; 19 | height: 20px; 20 | margin-bottom: 1px; 21 | } 22 | -------------------------------------------------------------------------------- /src/address-book/AddressBook.scss: -------------------------------------------------------------------------------- 1 | @import '../partial', '../vars', '../functions'; 2 | 3 | .AddressBook { 4 | .toolbar { 5 | @include address-book-padding-top; 6 | display: grid; 7 | grid-template-columns: auto 1fr auto; 8 | margin-bottom: 3rem; 9 | line-height: 30px; 10 | } 11 | 12 | .address-list { 13 | @extend %base-grid; 14 | 15 | grid-template-columns: repeat(3, auto); 16 | 17 | .row { 18 | display: contents; 19 | 20 | .address { 21 | @extend %monospace; 22 | } 23 | 24 | .label { 25 | @extend %ellipsize; 26 | max-width: 40vw; 27 | } 28 | 29 | .actions { 30 | text-align: right; 31 | 32 | > span { 33 | padding: 0.25em; 34 | } 35 | 36 | .delete { 37 | margin-right: 0.25em; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | .test { 45 | display: none; 46 | } 47 | -------------------------------------------------------------------------------- /src/address-book/AddressBook.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {action} from '@storybook/addon-actions' 3 | import {select, text} from '@storybook/addon-knobs' 4 | import {ESSENTIAL_DECORATORS} from '../storybook-util/essential-decorators' 5 | import {address1} from '../storybook-util/dummies' 6 | import {Address} from './Address' 7 | import {AddressBook, DeleteContactModal, EditContactModal} from './AddressBook' 8 | 9 | address1 10 | 11 | export default { 12 | title: 'Address book', 13 | decorators: ESSENTIAL_DECORATORS, 14 | } 15 | 16 | export const addressBook = (): JSX.Element => 17 | 18 | export const editableAddress = (): JSX.Element =>
19 | 20 | export const editContact = (): JSX.Element => ( 21 | 31 | ) 32 | 33 | export const deleteContact = (): JSX.Element => ( 34 | 41 | ) 42 | -------------------------------------------------------------------------------- /src/address-book/DialogAddressSelect.scss: -------------------------------------------------------------------------------- 1 | @import '../themify', '../partial', '../vars'; 2 | 3 | .DialogAddressSelect { 4 | .DialogColumns { 5 | grid-template-columns: 5fr 2fr; 6 | } 7 | 8 | .SelectContact { 9 | .label { 10 | @extend %dialog-label; 11 | display: block; 12 | 13 | @include themify($themes) { 14 | color: themed('dialog-label-color'); 15 | } 16 | } 17 | 18 | .ant-select-selector .ant-select-selection-item { 19 | max-width: 120px; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/antd-overrides/switch.scss: -------------------------------------------------------------------------------- 1 | @import '../sass-includes'; 2 | 3 | $inner-square-padding: 2px; 4 | $skew-margin: 6px; 5 | 6 | @include app-scoped { 7 | .ant-switch { 8 | @extend %add-focus-outline; 9 | margin-right: $skew-margin; 10 | margin-left: $skew-margin; 11 | transform: skew($button-skew, 0deg); 12 | border-radius: 0; 13 | 14 | @include themify($themes) { 15 | background-color: themed('switch-bg'); 16 | } 17 | 18 | .ant-switch-handle::before { 19 | top: $inner-square-padding; 20 | right: $inner-square-padding; 21 | bottom: $inner-square-padding; 22 | left: $inner-square-padding; 23 | border-radius: 0; 24 | 25 | @include themify($themes) { 26 | background-color: themed('switch-square'); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/bg-base-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/bg-base-dark.png -------------------------------------------------------------------------------- /src/assets/bg-base-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/bg-base-light.png -------------------------------------------------------------------------------- /src/assets/bg-moving-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/bg-moving-dark.png -------------------------------------------------------------------------------- /src/assets/bg-moving-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/bg-moving-light.png -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/fonts/Montserrat-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/fonts/Montserrat-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Montserrat-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/assets/fonts/Montserrat-SemiBold.ttf -------------------------------------------------------------------------------- /src/assets/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/chains/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 18 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /src/assets/icons/check-double.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/icons/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/icons/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/icons/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/icons/exchange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/hourglass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/icons/sum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | -------------------------------------------------------------------------------- /src/assets/logoCrop.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 38 | 39 | -------------------------------------------------------------------------------- /src/common/BorderlessInput.scss: -------------------------------------------------------------------------------- 1 | @import '../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .BorderlessInput { 5 | position: relative; 6 | 7 | .ant-input { 8 | width: 100%; 9 | height: $dialog-component-height; 10 | border: 0; 11 | border-radius: $dialog-component-border-radius; 12 | opacity: 1; 13 | resize: none; 14 | 15 | @include themify($themes) { 16 | border: 1px solid themed('text-color'); 17 | background-color: themed('dialog-dark-input-bg'); 18 | color: themed('text-color'); 19 | } 20 | 21 | &:focus { 22 | box-shadow: none; 23 | } 24 | 25 | &:hover, 26 | &:focus { 27 | @include themify($themes) { 28 | background-color: themed('dialog-dark-input-bg'); 29 | color: themed('text-color'); 30 | } 31 | } 32 | 33 | &::placeholder { 34 | @include themify($themes) { 35 | color: themed('secondary-text-color'); 36 | } 37 | } 38 | } 39 | 40 | &.invalid, 41 | .ant-form-item-has-error & { 42 | .ant-input { 43 | @include themify($themes) { 44 | border: 1px solid themed('error-color') !important; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/common/CopyableLongText.scss: -------------------------------------------------------------------------------- 1 | @import '../partial', '../vars'; 2 | 3 | .CopyableLongText { 4 | @extend %ellipsize; 5 | @extend %monospace; 6 | 7 | .clickable { 8 | margin-right: 0.5em; 9 | opacity: $secondary-opacity; 10 | cursor: pointer; 11 | 12 | &:hover { 13 | opacity: 1; 14 | } 15 | } 16 | } 17 | 18 | #Mantis .qr-code-popover.ant-popover > .ant-popover-content { 19 | .ant-popover-inner-content { 20 | background-color: #fff; 21 | } 22 | 23 | .ant-popover-arrow { 24 | border-color: #fff; 25 | background-color: #fff; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common/CopyableLongText.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from 'classnames' 3 | import {CopyOutlined, QrcodeOutlined} from '@ant-design/icons' 4 | import {Popover} from 'antd' 5 | import QRCode from 'qrcode.react' 6 | import {useTranslation, useLocalizedUtilities} from '../settings-state' 7 | import './CopyableLongText.scss' 8 | 9 | interface CopyableLongTextProps { 10 | content?: string | null 11 | showQrCode?: boolean 12 | fallback?: string 13 | className?: string 14 | } 15 | 16 | export const CopyableLongText = ({ 17 | content, 18 | showQrCode = false, 19 | fallback = '', 20 | className, 21 | }: CopyableLongTextProps): JSX.Element => { 22 | const {t} = useTranslation() 23 | const {copyToClipboard} = useLocalizedUtilities() 24 | 25 | return content ? ( 26 |
27 | {showQrCode && ( 28 | } 30 | placement="top" 31 | overlayClassName="qr-code-popover" 32 | > 33 | 34 | 35 | )} 36 | 37 | copyToClipboard(content)} /> 38 | 39 | 40 | {content} 41 | 42 |
43 | ) : ( 44 | <>{fallback} 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/common/Dialog.scss: -------------------------------------------------------------------------------- 1 | @import '../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .Dialog { 5 | width: $dialog-width; 6 | margin: auto; 7 | 8 | form { 9 | @include themify($themes) { 10 | color: themed('text-color'); 11 | } 12 | } 13 | 14 | .title { 15 | @extend %title; 16 | } 17 | 18 | .dialog-children { 19 | display: grid; 20 | margin-bottom: $dialog-spacing-before-buttons; 21 | } 22 | 23 | .actions { 24 | &.grid { 25 | @extend %two-col-grid-template; 26 | @extend %modal-diagonal-button; 27 | } 28 | 29 | &.natural > * { 30 | margin-right: 8px; 31 | } 32 | 33 | &.wide { 34 | display: grid; 35 | grid-row-gap: 8px; 36 | grid-template-rows: 1fr; 37 | 38 | .ant-btn { 39 | text-align: center; 40 | } 41 | } 42 | } 43 | 44 | .footer:not(:empty) { 45 | margin-top: $dialog-component-spacing; 46 | 47 | > *:last-child { 48 | margin-bottom: 0; 49 | } 50 | } 51 | 52 | .ant-form-item-explain { 53 | @extend %dialog-input-inline-error; 54 | 55 | width: $dialog-width; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/common/DismissableMessage.scss: -------------------------------------------------------------------------------- 1 | @import '../partial', '../vars'; 2 | 3 | .DismissableMessage { 4 | .dismiss-wrapper { 5 | text-align: right; 6 | 7 | .dismiss-link { 8 | @extend %link; 9 | font-size: $font-size-s; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/common/Header.scss: -------------------------------------------------------------------------------- 1 | @import '../partial', '../vars'; 2 | 3 | .Header { 4 | .external-link { 5 | margin: 0 0 0 1px; 6 | font-size: $font-size-s; 7 | } 8 | 9 | .right { 10 | float: right; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/common/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, {PropsWithChildren} from 'react' 2 | import {Link} from './Link' 3 | import './Header.scss' 4 | 5 | interface HeaderProps { 6 | externalLink?: { 7 | text: string 8 | url: string 9 | } 10 | } 11 | 12 | export const Header = ({externalLink, children}: PropsWithChildren): JSX.Element => ( 13 |
14 |
15 | {children} 16 | {externalLink && ( 17 |
18 | 19 | {externalLink.text} 20 | 21 |
22 | )} 23 |
24 |
25 | ) 26 | -------------------------------------------------------------------------------- /src/common/IconButton.scss: -------------------------------------------------------------------------------- 1 | @import '../sass-includes'; 2 | 3 | .icon { 4 | width: 16px; 5 | height: 16px; 6 | object-fit: contain; 7 | stroke-width: 0; 8 | 9 | @include themify($themes) { 10 | fill: themed('text-color'); 11 | } 12 | } 13 | 14 | .iconContainer { 15 | cursor: pointer; 16 | 17 | &:focus { 18 | box-shadow: none; 19 | @include themify($themes) { 20 | -webkit-filter: drop-shadow(2px 2px 1px themed('text-shadow')); 21 | filter: drop-shadow(2px 2px 1px themed('text-shadow')); 22 | } 23 | 24 | .icon { 25 | @include themify($themes) { 26 | fill: themed('primary-color'); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/common/IconButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SVG from 'react-inlinesvg' 3 | import {fillActionHandlers} from './util' 4 | import './IconButton.scss' 5 | 6 | type Icon = 7 | | 'arrow-down' 8 | | 'check-double' 9 | | 'check' 10 | | 'circle' 11 | | 'clock' 12 | | 'copy' 13 | | 'cross' 14 | | 'delete' 15 | | 'edit' 16 | | 'exchange' 17 | | 'hourglass' 18 | | 'loading' 19 | | 'refresh' 20 | | 'speed-high' 21 | | 'speed-low' 22 | | 'speed-medium' 23 | | 'sum' 24 | | 'wallet-restore' 25 | | 'wallet' 26 | 27 | interface IconButtonProps { 28 | icon: Icon 29 | title?: string 30 | onClick: () => void 31 | width?: number 32 | height?: number 33 | } 34 | 35 | export const IconButton = ({ 36 | icon, 37 | onClick, 38 | title, 39 | width = 16, 40 | height = 16, 41 | }: IconButtonProps): JSX.Element => ( 42 | 43 | 44 | 45 | ) 46 | -------------------------------------------------------------------------------- /src/common/InfoIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, {ReactNode} from 'react' 2 | import {InfoCircleOutlined} from '@ant-design/icons' 3 | import {Popover} from 'antd' 4 | import {PopoverProps} from 'antd/lib/popover' 5 | 6 | interface InfoIconProps { 7 | content: ReactNode 8 | placement?: PopoverProps['placement'] 9 | } 10 | 11 | export const InfoIcon = ({content, placement}: InfoIconProps): JSX.Element => ( 12 | 13 | 14 | 15 | ) 16 | -------------------------------------------------------------------------------- /src/common/InlineError.scss: -------------------------------------------------------------------------------- 1 | @import '../partial'; 2 | 3 | .InlineError { 4 | position: relative; 5 | 6 | .error-message { 7 | @extend %dialog-input-inline-error; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/common/InlineError.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent, PropsWithChildren} from 'react' 2 | import classnames from 'classnames' 3 | import './InlineError.scss' 4 | 5 | export interface InlineErrorProps { 6 | errorMessage?: string 7 | className?: string 8 | forceInvalid?: boolean 9 | } 10 | 11 | export const InlineError: FunctionComponent = ({ 12 | errorMessage, 13 | className, 14 | forceInvalid, 15 | children, 16 | }: PropsWithChildren) => ( 17 |
18 | {errorMessage &&
{errorMessage}
} 19 | {children} 20 |
21 | ) 22 | -------------------------------------------------------------------------------- /src/common/Link.scss: -------------------------------------------------------------------------------- 1 | @import '../partial'; 2 | 3 | .Link { 4 | @extend %disable-focus-outline; 5 | 6 | cursor: pointer; 7 | 8 | &:focus { 9 | text-decoration: underline; 10 | } 11 | 12 | &.styled { 13 | @extend %link; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/common/Link.tsx: -------------------------------------------------------------------------------- 1 | import React, {PropsWithChildren} from 'react' 2 | import _ from 'lodash' 3 | import {shell} from 'electron' 4 | import {Popover} from 'antd' 5 | import {TooltipPlacement} from 'antd/lib/tooltip' 6 | import classnames from 'classnames' 7 | import {fillActionHandlers} from './util' 8 | import './Link.scss' 9 | 10 | interface LinkProps { 11 | href: string 12 | className?: string 13 | styled?: boolean 14 | popoverPlacement?: TooltipPlacement | undefined 15 | } 16 | 17 | export const Link = ({ 18 | children, 19 | href, 20 | className = '', 21 | styled = false, 22 | popoverPlacement = undefined, 23 | }: PropsWithChildren): JSX.Element => ( 24 | { 27 | event?.preventDefault() 28 | shell.openExternal(href) 29 | })} 30 | > 31 | 32 | {children} 33 | 34 | 35 | ) 36 | -------------------------------------------------------------------------------- /src/common/Loading.scss: -------------------------------------------------------------------------------- 1 | .Loading { 2 | width: 100%; 3 | height: 100%; 4 | 5 | .loading-icon { 6 | display: block; 7 | position: relative; 8 | top: 50%; 9 | left: 50%; 10 | width: 50px; 11 | height: 50px; 12 | animation: spin 4s linear infinite; 13 | } 14 | } 15 | 16 | @keyframes spin { 17 | 100% { 18 | transform: rotate(360deg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/common/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SVG from 'react-inlinesvg' 3 | import loadingIcon from '../assets/icons/loading.svg' 4 | import './Loading.scss' 5 | 6 | export const Loading = (): JSX.Element => { 7 | return ( 8 |
9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/common/MantisModal.scss: -------------------------------------------------------------------------------- 1 | @import '../themify', '../vars'; 2 | 3 | .MantisModal.ant-modal { 4 | @include themify($themes) { 5 | color: themed('text-color'); 6 | } 7 | 8 | .ant-modal-body { 9 | padding: $modal-body-padding; 10 | } 11 | 12 | .ant-modal-content { 13 | border-radius: $dialog-component-border-radius * 2; 14 | box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.4), 0 6px 16px 0 rgba(0, 0, 0, 0.01), 15 | 0 9px 28px 8px rgba(0, 0, 0, 0.2); 16 | 17 | @include themify($themes) { 18 | background: themed('bg-moving'), themed('modal-background'); 19 | background-position: right top; 20 | background-size: cover; 21 | } 22 | } 23 | 24 | .ant-modal-close { 25 | margin: 29px 22px 0 0; 26 | } 27 | 28 | .ant-modal-footer { 29 | padding: 0; 30 | border-top-width: 0; 31 | } 32 | } 33 | 34 | .ScrollableModalFooter { 35 | max-height: 225px; 36 | padding: 1.25rem 64px; 37 | overflow: auto; 38 | border-radius: 0 0 $dialog-component-border-radius * 2 $dialog-component-border-radius * 2; 39 | text-align: left; 40 | 41 | @include themify($themes) { 42 | background-color: themed('dialog-dark-input-bg'); 43 | } 44 | 45 | &::-webkit-scrollbar-track { 46 | margin-bottom: 28px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/common/ShortNumber.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Popover} from 'antd' 3 | import BigNumber from 'bignumber.js' 4 | import {useFormatters} from '../settings-state' 5 | import {ETC_CHAIN} from './chains' 6 | 7 | interface ShortNumberProps { 8 | big: BigNumber | number 9 | decimals?: number 10 | showSign?: boolean 11 | content?: React.ReactNode 12 | } 13 | 14 | export const ShortNumber = ({ 15 | big: maybeBig, 16 | decimals = ETC_CHAIN.decimals, 17 | showSign = false, 18 | content = null, 19 | }: ShortNumberProps): JSX.Element => { 20 | const {abbreviateAmount} = useFormatters() 21 | 22 | const big = new BigNumber(maybeBig) 23 | const {relaxed, strict} = abbreviateAmount(big.shiftedBy(-decimals)) 24 | const prefix = showSign && big.isGreaterThan(0) ? '+' : '' 25 | 26 | return ( 27 | 28 | 29 | {prefix} 30 | {strict} 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/common/SupportModal.scss: -------------------------------------------------------------------------------- 1 | @import '../partial'; 2 | 3 | .SupportModal { 4 | .link { 5 | @extend %disable-focus-outline; 6 | cursor: 'pointer'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/common/SyncStatus.scss: -------------------------------------------------------------------------------- 1 | @import '../themify', '../vars', '../partial'; 2 | 3 | .SyncStatus { 4 | font-size: $font-size-s; 5 | font-weight: bold; 6 | 7 | @include themify($themes) { 8 | color: themed('inactive-text-color'); 9 | } 10 | 11 | svg { 12 | height: 12px; 13 | margin: 0 0 2px 2px; 14 | vertical-align: middle; 15 | } 16 | 17 | &.online svg { 18 | @include themify($themes) { 19 | animation: spin 4s linear infinite; 20 | stroke: themed('primary-color'); 21 | } 22 | } 23 | 24 | &.offline svg { 25 | animation: spin 4s linear infinite; 26 | 27 | @include themify($themes) { 28 | stroke: themed('text-color'); 29 | } 30 | } 31 | 32 | &.synced svg { 33 | @include themify($themes) { 34 | stroke: themed('text-color'); 35 | } 36 | } 37 | 38 | .network { 39 | font-weight: 500; 40 | } 41 | } 42 | 43 | .ant-popover { 44 | .ant-popover-inner-content { 45 | max-width: 250px; 46 | } 47 | 48 | .syncStatusLine > strong { 49 | @extend %monospace; 50 | font-weight: 700; 51 | } 52 | } 53 | 54 | @keyframes spin { 55 | 100% { 56 | transform: rotate(-360deg); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/common/Trans.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Trans as TransNext, TransProps as TransPropsNext} from 'react-i18next' 3 | import {TKeyRenderer, tKeyRendererToString} from './i18n' 4 | import {useTranslation} from '../settings-state' 5 | 6 | type TransProps = Omit, 'i18nKey' | 'i18n'> & { 7 | k: TKeyRenderer 8 | } 9 | 10 | export const Trans = ({k, ...props}: TransProps): JSX.Element => { 11 | const {i18n} = useTranslation() 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /src/common/chains.ts: -------------------------------------------------------------------------------- 1 | import ethereumLogo from '../assets/icons/chains/ethereum.svg' 2 | 3 | export interface Chain { 4 | symbol: string 5 | logo: string 6 | decimals: number 7 | } 8 | 9 | export const ETC_CHAIN: Chain = { 10 | symbol: 'ETC', 11 | logo: ethereumLogo, 12 | decimals: 18, 13 | } 14 | -------------------------------------------------------------------------------- /src/common/clipboard.ts: -------------------------------------------------------------------------------- 1 | import {message} from 'antd' 2 | import {wait} from '../shared/utils' 3 | import {UI_DELAY} from './constants/ui' 4 | 5 | export const copyToClipboard = async (text: string, successMessage: string): Promise => { 6 | await navigator.clipboard.writeText(text) 7 | await wait(UI_DELAY) 8 | 9 | message.success(successMessage) 10 | } 11 | -------------------------------------------------------------------------------- /src/common/constants/ui.ts: -------------------------------------------------------------------------------- 1 | // ms, used in cases when we'd like to show an animation before the action finishes 2 | export const UI_DELAY = 399 3 | -------------------------------------------------------------------------------- /src/common/dialog/DialogApproval.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | $padding-v: 21px; 4 | $padding-h: 17px; 5 | 6 | @include dialog-scoped { 7 | .DialogApproval { 8 | margin-bottom: $dialog-component-spacing; 9 | 10 | .checkbox-wrapper { 11 | border-radius: $dialog-component-border-radius; 12 | 13 | @include themify($themes) { 14 | border: 1px solid themed('text-color'); 15 | background-color: themed('input-bg'); 16 | color: themed('text-color'); 17 | } 18 | 19 | .dark & { 20 | @include themify($themes) { 21 | background-color: themed('dialog-dark-input-bg'); 22 | } 23 | } 24 | } 25 | 26 | .checkbox { 27 | display: grid; 28 | grid-column-gap: 5px; 29 | grid-template-columns: auto 1fr; 30 | padding: $padding-v $padding-h; 31 | font-size: $font-size-s; 32 | 33 | @include themify($themes) { 34 | color: rgba($color: themed('text-color'), $alpha: $unselected-opacity); 35 | } 36 | 37 | .ant-checkbox { 38 | align-self: center; 39 | } 40 | } 41 | 42 | .extra { 43 | padding: $padding-v $padding-h 0; 44 | } 45 | 46 | .ant-form-item-has-error .checkbox-wrapper .ant-checkbox-inner { 47 | @include themify($themes) { 48 | border: 1px solid themed('error-color'); 49 | background-color: themed('error-color'); 50 | } 51 | } 52 | 53 | .approval-form-item .ant-form-item-explain { 54 | display: none; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/common/dialog/DialogApproval.tsx: -------------------------------------------------------------------------------- 1 | import React, {Ref, forwardRef, ForwardRefRenderFunction, PropsWithChildren} from 'react' 2 | import {Checkbox, Form} from 'antd' 3 | import {CheckboxProps} from 'antd/lib/checkbox' 4 | import './DialogApproval.scss' 5 | 6 | interface DialogApprovalProps extends Omit { 7 | id?: string 8 | description: React.ReactNode 9 | onChange?: (checked: boolean) => void 10 | } 11 | 12 | export const _DialogApproval: ForwardRefRenderFunction = ( 13 | {id, description, onChange, children, ...rest}: PropsWithChildren, 14 | ref: Ref, 15 | ): JSX.Element => ( 16 |
17 | { 24 | return value ? Promise.resolve() : Promise.reject('Checkbox not checked') 25 | }, 26 | }, 27 | ]} 28 | > 29 |
30 | {children &&
{children}
} 31 | onChange?.(e.target.checked)} 35 | {...rest} 36 | ref={ref} 37 | > 38 | {description} 39 | 40 |
41 |
42 |
43 | ) 44 | 45 | export const DialogApproval = forwardRef(_DialogApproval) 46 | -------------------------------------------------------------------------------- /src/common/dialog/DialogColumns.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogColumns { 5 | @extend %two-col-grid-template; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/common/dialog/DialogColumns.tsx: -------------------------------------------------------------------------------- 1 | import React, {PropsWithChildren, FunctionComponent} from 'react' 2 | import './DialogColumns.scss' 3 | 4 | export const DialogColumns: FunctionComponent<{}> = ({children}: PropsWithChildren<{}>) => ( 5 |
{children}
6 | ) 7 | -------------------------------------------------------------------------------- /src/common/dialog/DialogDisplayWords.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogDisplayWords { 5 | display: grid; 6 | grid-gap: 10px; 7 | grid-template-columns: 1fr 1fr 1fr 1fr; 8 | margin-bottom: $dialog-component-spacing; 9 | 10 | .word { 11 | height: 30px; 12 | border-radius: $dialog-component-border-radius; 13 | font-size: $font-size-s; 14 | font-weight: bold; 15 | line-height: 30px; 16 | user-select: none; 17 | overflow-wrap: break-word; 18 | 19 | @include themify($themes) { 20 | background-color: themed('secondary-background'); 21 | } 22 | 23 | .index { 24 | width: 38px; 25 | padding-left: 1em; 26 | float: left; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/common/dialog/DialogDisplayWords.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent} from 'react' 2 | import './DialogDisplayWords.scss' 3 | 4 | interface DialogDisplayWordsProps { 5 | words: string[] 6 | } 7 | 8 | export const DialogDisplayWords: FunctionComponent = ({ 9 | words, 10 | }: DialogDisplayWordsProps) => ( 11 |
12 | {words.map((word, i) => ( 13 |
14 | {i + 1}. {word} 15 |
16 | ))} 17 |
18 | ) 19 | -------------------------------------------------------------------------------- /src/common/dialog/DialogDropdown.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogDropdown { 5 | margin-bottom: $dialog-component-spacing; 6 | 7 | .active-option { 8 | width: 508px; 9 | padding: 6px 0 0; 10 | overflow: hidden; 11 | font-size: $font-size-m; 12 | font-weight: bold; 13 | white-space: nowrap; 14 | 15 | &.no-option { 16 | opacity: $secondary-opacity; 17 | } 18 | } 19 | 20 | .label { 21 | @extend %dialog-label; 22 | font-weight: normal; 23 | cursor: pointer; 24 | } 25 | } 26 | 27 | .DialogDropdownOverlay { 28 | .ant-dropdown-menu { 29 | @extend %disable-focus-outline; 30 | 31 | @include themify($themes) { 32 | background-color: themed('normal-color'); 33 | color: themed('text-color'); 34 | } 35 | } 36 | 37 | .ant-dropdown-menu-item { 38 | @extend %disable-focus-outline; 39 | 40 | width: 508px; 41 | overflow: hidden; 42 | 43 | @include themify($themes) { 44 | background-color: themed('normal-color'); 45 | color: themed('text-color'); 46 | } 47 | 48 | &:hover { 49 | @include themify($themes) { 50 | background-color: themed('primary-color'); 51 | color: #000; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/common/dialog/DialogError.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogError { 5 | margin-bottom: $dialog-component-spacing; 6 | padding: 15px; 7 | border-radius: $dialog-component-border-radius; 8 | font-size: $font-size-s; 9 | text-align: center; 10 | 11 | @include themify($themes) { 12 | background-color: themed('error-background-color'); 13 | color: themed('error-dialog-color'); 14 | } 15 | 16 | .help { 17 | padding-left: 3px; 18 | font-weight: bold; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/common/dialog/DialogError.tsx: -------------------------------------------------------------------------------- 1 | import React, {PropsWithChildren} from 'react' 2 | import {Link} from '../Link' 3 | import {Trans} from '../Trans' 4 | import './DialogError.scss' 5 | 6 | interface DialogErrorProps { 7 | helpURL?: string | null 8 | } 9 | 10 | export const DialogError = ({ 11 | children, 12 | helpURL = null, 13 | }: PropsWithChildren): JSX.Element => ( 14 |
15 | {children} 16 | {helpURL && ( 17 | 18 | 19 | 20 | )} 21 |
22 | ) 23 | -------------------------------------------------------------------------------- /src/common/dialog/DialogInput.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogInput { 5 | position: relative; 6 | margin-bottom: $dialog-component-spacing; 7 | 8 | .label { 9 | @extend %dialog-label; 10 | display: block; 11 | 12 | @include themify($themes) { 13 | color: themed('dialog-label-color'); 14 | } 15 | 16 | .optional { 17 | @include themify($themes) { 18 | color: themed('secondary-text-color'); 19 | } 20 | } 21 | } 22 | 23 | .input { 24 | height: $dialog-component-height; 25 | min-height: $dialog-component-height; 26 | border-radius: $dialog-component-border-radius; 27 | } 28 | 29 | textarea.input { 30 | padding: 12px; 31 | } 32 | 33 | .dark & { 34 | .ant-input { 35 | @include themify($themes) { 36 | border: 1px solid themed('text-color'); 37 | background-color: themed('dialog-dark-input-bg'); 38 | color: themed('secondary-text-color'); 39 | } 40 | } 41 | 42 | .input { 43 | @include themify($themes) { 44 | color: themed('secondary-text-color'); 45 | } 46 | 47 | svg { 48 | @include themify($themes) { 49 | stroke: themed('text-color'); 50 | fill: themed('text-color'); 51 | } 52 | } 53 | } 54 | 55 | .fill-button { 56 | @include themify($themes) { 57 | background-color: themed('tertiary-background'); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/common/dialog/DialogMessage.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogMessage { 5 | margin-bottom: $dialog-component-spacing; 6 | 7 | .label { 8 | @extend %dialog-label; 9 | 10 | @include themify($themes) { 11 | color: themed('dialog-label-color'); 12 | } 13 | } 14 | 15 | .description { 16 | font-size: $font-size-m; 17 | } 18 | 19 | &.highlight .description { 20 | padding: 1rem; 21 | border-radius: $dialog-component-border-radius; 22 | 23 | @include themify($themes) { 24 | background-color: themed('normal-color'); 25 | color: themed('text-color'); 26 | } 27 | } 28 | 29 | .dark &.highlight .description { 30 | @include themify($themes) { 31 | background-color: themed('dialog-dark-input-bg'); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/common/dialog/DialogMessage.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent, PropsWithChildren} from 'react' 2 | import classnames from 'classnames' 3 | import './DialogMessage.scss' 4 | 5 | interface DialogMessageProps { 6 | label?: string 7 | type?: 'default' | 'highlight' 8 | } 9 | 10 | export const DialogMessage: FunctionComponent = ({ 11 | label, 12 | children, 13 | type = 'default', 14 | }: PropsWithChildren) => ( 15 |
16 | {label &&
{label}
} 17 |
{children}
18 |
19 | ) 20 | -------------------------------------------------------------------------------- /src/common/dialog/DialogPassword.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogPassword { 5 | position: relative; 6 | margin-bottom: $dialog-component-spacing; 7 | 8 | .label { 9 | @extend %dialog-label; 10 | 11 | @include themify($themes) { 12 | color: themed('dialog-label-color'); 13 | } 14 | } 15 | 16 | .inputs { 17 | @extend %two-col-grid-template; 18 | } 19 | 20 | .input { 21 | height: $dialog-component-height; 22 | } 23 | 24 | .criteria { 25 | margin-top: 5px; 26 | opacity: $secondary-opacity; 27 | font-size: $font-size-s; 28 | font-weight: bold; 29 | } 30 | 31 | .password-form-item .ant-form-item-explain { 32 | display: none; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/common/dialog/DialogPassword.test.ts: -------------------------------------------------------------------------------- 1 | import {assert} from 'chai' 2 | import {hasAllNeededCharacters} from './DialogPassword' 3 | 4 | it('checks password validity correctly', async (): Promise => { 5 | assert.notEqual(hasAllNeededCharacters(''), 'OK', 'Empty string is invalid') 6 | assert.notEqual(hasAllNeededCharacters('abcdefghIJKLMNOP'), 'OK', 'String is missing numbers') 7 | assert.notEqual( 8 | hasAllNeededCharacters('0123456789QRSTUVWXYZ'), 9 | 'OK', 10 | 'String is missing lowercase characters', 11 | ) 12 | assert.notEqual( 13 | hasAllNeededCharacters('0123456789qrstuvwxyz'), 14 | 'OK', 15 | 'String is missing uppercase characters', 16 | ) 17 | assert.equal(hasAllNeededCharacters('0aA'), 'OK', 'String should be valid') 18 | }) 19 | -------------------------------------------------------------------------------- /src/common/dialog/DialogQRCode.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogQRCode { 5 | margin-bottom: $dialog-component-spacing; 6 | transition: filter $transition-duration; 7 | 8 | &.blurred { 9 | transition: filter $transition-duration; 10 | pointer-events: none; 11 | filter: blur(4px); 12 | } 13 | 14 | .display { 15 | display: grid; 16 | grid-column-gap: 1.5rem; 17 | grid-template-columns: auto 1fr; 18 | margin-bottom: 1rem; 19 | } 20 | 21 | .qr-content { 22 | @extend %monospace; 23 | font-size: $font-size-m; 24 | } 25 | 26 | .download { 27 | padding: 0 30px; 28 | letter-spacing: $letter-spacing-large; 29 | text-transform: uppercase; 30 | } 31 | 32 | .qr-code { 33 | padding: 5px 5px 0 5px; 34 | background-color: #fff; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/common/dialog/DialogQRCode.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent} from 'react' 2 | import classnames from 'classnames' 3 | import {Button} from 'antd' 4 | import QRCode from 'qrcode.react' 5 | import fileDownload from 'js-file-download' 6 | import {DialogColumns} from './DialogColumns' 7 | import {Trans} from '../Trans' 8 | import './DialogQRCode.scss' 9 | 10 | interface DialogQRCodeProps { 11 | content: string 12 | downloadFileName?: string 13 | blurred?: boolean 14 | } 15 | 16 | export const DialogQRCode: FunctionComponent = ({ 17 | content, 18 | downloadFileName, 19 | blurred = false, 20 | }: DialogQRCodeProps) => ( 21 |
22 |
23 |
24 | 25 |
26 |
{content}
27 |
28 | {downloadFileName && ( 29 | 30 | 37 | 38 | )} 39 |
40 | ) 41 | -------------------------------------------------------------------------------- /src/common/dialog/DialogSecrets.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogSecrets { 5 | margin-bottom: $dialog-component-spacing; 6 | 7 | .tabs { 8 | height: 35px; 9 | padding: 4px 0; 10 | 11 | .tab { 12 | @extend %disable-focus-outline; 13 | 14 | display: inline-block; 15 | padding: 0 1em; 16 | font-size: $font-size-s; 17 | text-transform: uppercase; 18 | cursor: pointer; 19 | 20 | @include themify($themes) { 21 | border-left: 1px solid rgba($color: themed('text-color'), $alpha: $secondary-opacity); 22 | color: rgba($color: themed('text-color'), $alpha: $secondary-opacity); 23 | } 24 | 25 | &:hover, 26 | &:focus, 27 | &.active { 28 | @include themify($themes) { 29 | color: themed('text-color'); 30 | } 31 | } 32 | 33 | &:first-child { 34 | padding-left: 0; 35 | border-left: none !important; 36 | } 37 | } 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/common/dialog/DialogSeedPhrase.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogSeedPhrase { 5 | margin-bottom: $dialog-component-spacing; 6 | 7 | .select { 8 | width: 100%; 9 | } 10 | 11 | .select .ant-input { 12 | height: $dialog-component-height; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/common/dialog/DialogShowAmount.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogShowAmount { 5 | $height: 50px; 6 | margin-bottom: $dialog-component-spacing; 7 | 8 | .svg { 9 | @include themify($themes) { 10 | stroke: themed('text-color'); 11 | fill: themed('text-color'); 12 | } 13 | } 14 | 15 | .label { 16 | @extend %dialog-label; 17 | 18 | @include themify($themes) { 19 | color: themed('dialog-label-color'); 20 | } 21 | 22 | .note { 23 | opacity: $secondary-opacity; 24 | text-transform: none; 25 | } 26 | } 27 | 28 | .container { 29 | display: grid; 30 | grid-template-columns: auto 1fr; 31 | padding: 0 0.75rem; 32 | border-radius: $dialog-component-border-radius; 33 | cursor: not-allowed; 34 | 35 | @include themify($themes) { 36 | border: 1px solid themed('text-color'); 37 | background-color: themed('dialog-dark-input-bg'); 38 | color: themed('text-color'); 39 | } 40 | 41 | .logo { 42 | width: 20px; 43 | 44 | .asset-icon { 45 | @extend %asset-icon; 46 | top: 14px; 47 | } 48 | } 49 | 50 | .amount { 51 | overflow: hidden; 52 | line-height: $height; 53 | text-overflow: ellipsis; 54 | word-break: break-word; 55 | white-space: nowrap; 56 | } 57 | 58 | &.invalid { 59 | @include themify($themes) { 60 | border: 1px solid themed('error-color'); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/common/dialog/DialogShowAmount.tsx: -------------------------------------------------------------------------------- 1 | import React, {PropsWithChildren} from 'react' 2 | import SVG from 'react-inlinesvg' 3 | import classnames from 'classnames' 4 | import {StopOutlined} from '@ant-design/icons' 5 | import {useFormatters} from '../../settings-state' 6 | import {ShortNumber} from '../ShortNumber' 7 | import {ETC_CHAIN} from '../chains' 8 | import {Wei, etherValue} from '../units' 9 | import './DialogShowAmount.scss' 10 | 11 | interface DialogShowAmountProps { 12 | amount: Wei 13 | displayExact?: boolean 14 | } 15 | 16 | const ShowAmount = ({amount, displayExact}: DialogShowAmountProps): JSX.Element => { 17 | const {abbreviateAmount} = useFormatters() 18 | 19 | if (!amount.isFinite()) { 20 | return 21 | } 22 | 23 | return displayExact ? ( 24 | <>{abbreviateAmount(etherValue(amount)).relaxed} 25 | ) : ( 26 | 27 | ) 28 | } 29 | 30 | export const DialogShowAmount = ({ 31 | amount, 32 | displayExact, 33 | children, 34 | }: PropsWithChildren): JSX.Element => { 35 | const invalid = !amount.isPositive() 36 | 37 | return ( 38 |
39 |
{children}
40 |
41 |
42 | 43 |
44 |
45 | {ETC_CHAIN.symbol} 46 |
47 |
48 |
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/common/dialog/DialogSwitch.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .DialogSwitch { 5 | margin-bottom: $dialog-component-spacing; 6 | 7 | .label { 8 | @extend %dialog-label; 9 | 10 | @include themify($themes) { 11 | color: themed('dialog-label-color'); 12 | } 13 | } 14 | 15 | .switch { 16 | display: grid; 17 | grid-column-gap: 10px; 18 | grid-template-columns: 1fr auto; 19 | font-size: $font-size-s; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/common/dialog/DialogSwitch.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent} from 'react' 2 | import Switch, {SwitchProps} from 'antd/lib/switch' 3 | import './DialogSwitch.scss' 4 | 5 | interface DialogCheckProps { 6 | label: string 7 | description: string 8 | } 9 | 10 | export const DialogSwitch: FunctionComponent = ({ 11 | label, 12 | description, 13 | ...rest 14 | }: DialogCheckProps & SwitchProps) => ( 15 |
16 |
{label}
17 |
18 |
{description}
19 | 20 |
21 |
22 | ) 23 | -------------------------------------------------------------------------------- /src/common/i18n-pseudo.ts: -------------------------------------------------------------------------------- 1 | import {PostProcessorModule, TOptions} from 'i18next' 2 | import {DEFAULT_LANGUAGE} from '../shared/i18n' 3 | 4 | const lettersToChange: Record = { 5 | a: 'ää', 6 | e: 'ḝḝ', 7 | i: 'ɨɨ', 8 | o: 'ỡỡ', 9 | u: 'ṵṵ', 10 | y: 'ẙẙ', 11 | A: 'ḀḀ', 12 | E: 'ƐƐ', 13 | I: 'ḬḬ', 14 | O: 'ṎṎ', 15 | U: 'ṲṲ', 16 | Y: 'ŶŶ', 17 | } as const 18 | 19 | export const pseudoLanguage: PostProcessorModule = { 20 | name: 'pseudoLanguage', 21 | type: 'postProcessor', 22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 23 | process: (value: string, _key: string, _options: TOptions, translator: any): string => { 24 | if (translator?.language !== DEFAULT_LANGUAGE) { 25 | return value 26 | } 27 | const processedValue = value 28 | .split('') 29 | .map((letter) => lettersToChange[letter] ?? letter) 30 | .join('') 31 | return `·${processedValue}·` 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /src/common/io-helpers.test.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 2 | import * as t from 'io-ts' 3 | import {assert} from 'chai' 4 | import {right, isLeft} from 'fp-ts/lib/Either' 5 | import BigNumber from 'bignumber.js' 6 | import {BigNumberFromHexString, SignatureParamCodec} from './io-helpers' 7 | 8 | it('BigNumberFromHexString deserializes hex strings into BigNumbers', () => 9 | ['0x0', '0x1', '0x123', '0xFFF'].forEach((input) => { 10 | assert.deepEqual(BigNumberFromHexString.decode(input), right(new BigNumber(input))) 11 | })) 12 | 13 | it('BigNumberFromHexString rejects invalid hex numbers', () => 14 | ['123', 'foobar'].forEach((input) => assert(isLeft(BigNumberFromHexString.decode(input))))) 15 | 16 | it('SignatureParamCodec pads signatures correctly ', () => { 17 | assert.deepEqual( 18 | SignatureParamCodec.decode('0x11111'), 19 | right('0x0000000000000000000000000000000000000000000000000000000000011111'), 20 | ) 21 | }) 22 | 23 | it('SignatureParamCodec rejects too long signatures ', () => { 24 | assert( 25 | isLeft( 26 | SignatureParamCodec.decode( 27 | '0x00000000000000000000000000000000000000000000000000000000000111111', 28 | ), 29 | ), 30 | ) 31 | }) 32 | -------------------------------------------------------------------------------- /src/common/io-helpers.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import * as t from 'io-ts' 3 | import {either} from 'fp-ts/lib/Either' 4 | 5 | export const BigNumberFromHexString = new t.Type( 6 | 'BigNumberFromHexString', 7 | (u): u is BigNumber => u instanceof BigNumber, 8 | (u, c) => 9 | either.chain(t.string.validate(u, c), (str) => { 10 | if (!str.startsWith('0x')) return t.failure(str, c) 11 | const bigNumber = new BigNumber(str) 12 | return bigNumber.isFinite() ? t.success(bigNumber) : t.failure(str, c) 13 | }), 14 | (a) => a.toString(16), 15 | ) 16 | 17 | export const NumberFromHexString = new t.Type( 18 | 'NumberFromHexString', 19 | t.number.is, 20 | (u, c) => 21 | either.chain(t.string.validate(u, c), (str) => { 22 | if (!str.startsWith('0x')) return t.failure(str, c) 23 | const number = parseInt(str, 16) 24 | return !isNaN(number) ? t.success(number) : t.failure(str, c) 25 | }), 26 | (a) => a.toString(16), 27 | ) 28 | 29 | export const SignatureParamCodec = new t.Type( 30 | 'SignatureParamCodec', 31 | t.string.is, 32 | (u, c) => 33 | either.chain(t.string.validate(u, c), (str) => { 34 | if (str.length > 66) return t.failure(str, c) 35 | if (str.length == 66) return t.success(str) 36 | return t.success(`0x${str.slice(2).padStart(64, '0')}`) 37 | }), 38 | t.identity, 39 | ) 40 | -------------------------------------------------------------------------------- /src/common/ipc-util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import {ipcRenderer} from 'electron' 3 | import {IPCToRendererChannelName, IPCFromRendererChannelName} from '../shared/ipc-types' 4 | import {Language} from '../shared/i18n' 5 | import {NetworkName} from '../config/type' 6 | 7 | // General typed wrappers 8 | 9 | export const ipcListenToMain = ( 10 | channel: IPCToRendererChannelName, 11 | listener: Parameters[1], 12 | ): void => { 13 | ipcRenderer.on(channel, listener) 14 | } 15 | 16 | export const ipcRemoveAllListeners = (channel: IPCToRendererChannelName): void => { 17 | ipcRenderer.removeAllListeners(channel) 18 | } 19 | 20 | export const ipcSend = (channel: IPCFromRendererChannelName, ...args: any[]): void => { 21 | ipcRenderer.send(channel, ...args) 22 | } 23 | 24 | // Specific actions 25 | 26 | export const updateLanguage = (language: Language): void => { 27 | ipcSend('update-language', language) 28 | } 29 | 30 | export const saveDebugLogs = (rendererStoreData: string): void => { 31 | ipcSend('save-debug-logs', rendererStoreData) 32 | } 33 | 34 | export const updateNetworkName = (networkName: NetworkName): void => { 35 | ipcSend('update-network-name', networkName) 36 | } 37 | 38 | export const updateDatadirLocation = (): void => { 39 | ipcSend('update-datadir-location') 40 | } 41 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import {config} from '../config/renderer' 3 | import {createLogger} from '../shared/utils' 4 | 5 | export const rendererLog = createLogger('renderer', () => 6 | path.join(config.walletDataDir, 'logs', 'renderer.log'), 7 | ) 8 | -------------------------------------------------------------------------------- /src/common/mnemonic.ts: -------------------------------------------------------------------------------- 1 | import {generateMnemonic, mnemonicToSeed, validateMnemonic} from 'bip39' 2 | import {hdkey} from 'ethereumjs-wallet' 3 | import {createTErrorRenderer} from './i18n' 4 | 5 | interface RecoverySecrets { 6 | seedPhrase: string[] 7 | privateKey: string 8 | } 9 | 10 | export async function generatePrivateKeyFromSeedPhrase(seedPhrase: string): Promise { 11 | if (!validateMnemonic(seedPhrase)) { 12 | throw createTErrorRenderer(['common', 'error', 'invalidSeedPhrase']) 13 | } 14 | 15 | const seed = await mnemonicToSeed(seedPhrase) 16 | 17 | return hdkey.fromMasterSeed(seed).derivePath("m/44'/60'/0/0/0").getWallet().getPrivateKeyString() 18 | } 19 | 20 | export async function createNewAccount(): Promise { 21 | const mnemonic = generateMnemonic() 22 | 23 | const privateKey = await generatePrivateKeyFromSeedPhrase(mnemonic) 24 | 25 | return { 26 | seedPhrase: mnemonic.split(' '), 27 | privateKey, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/common/notify.ts: -------------------------------------------------------------------------------- 1 | import {rendererLog} from './logger' 2 | 3 | export const makeDesktopNotification = ( 4 | body: string, 5 | title: string, 6 | options: NotificationOptions = {}, 7 | ): void => { 8 | if (body.length > 256) { 9 | rendererLog.error('Notification body will be truncated on macOS (max 256 chars)') 10 | rendererLog.info({notificationBody: body}) 11 | } 12 | 13 | if (document.hasFocus()) { 14 | return // Do not show notifications if the document has focus 15 | } 16 | 17 | const show = (): void => { 18 | new Notification(title, { 19 | body, 20 | ...options, 21 | }) 22 | } 23 | 24 | if (Notification.permission === 'granted') { 25 | show() 26 | } else if (Notification.permission !== 'denied') { 27 | Notification.requestPermission().then((permission) => { 28 | // If the user accepts, let's create a notification 29 | if (permission === 'granted') { 30 | show() 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/common/units.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import {Branded} from 'io-ts' 3 | import {ETC_CHAIN} from './chains' 4 | 5 | export type Wei = Branded 6 | export const Wei = { 7 | zero: asWei(0), 8 | } 9 | 10 | export function asEther(v: BigNumber.Value): Wei { 11 | return new BigNumber(v).shiftedBy(ETC_CHAIN.decimals) as Wei 12 | } 13 | 14 | export function asWei(v: BigNumber.Value): Wei { 15 | return new BigNumber(v) as Wei 16 | } 17 | 18 | export function etherValue(wei: Wei): BigNumber { 19 | return wei.shiftedBy(-ETC_CHAIN.decimals) 20 | } 21 | -------------------------------------------------------------------------------- /src/common/wallet-status-guard.tsx: -------------------------------------------------------------------------------- 1 | import React, {ComponentType} from 'react' 2 | import {WalletState, WalletData, WalletStatus} from './wallet-state' 3 | import {Filter} from '../shared/typeUtils' 4 | 5 | export type PropsWithWalletState = { 6 | walletState: TState 7 | } & TProps 8 | 9 | type StateByStatus = Filter 10 | 11 | const isInStatus = ( 12 | walletState: WalletData, 13 | status: TStatus, 14 | ): walletState is StateByStatus => { 15 | return walletState.walletStatus === status 16 | } 17 | 18 | interface MessageProps { 19 | walletStatus: WalletStatus 20 | } 21 | const DefaultMessage = ({walletStatus}: MessageProps): JSX.Element => ( 22 | <>Wrong status: {walletStatus} 23 | ) 24 | 25 | // Adds walletState to the props of the decorated component 26 | // TState specifies the valid WalletState for the component 27 | export function withStatusGuard( 28 | Component: (props: PropsWithWalletState>) => JSX.Element, 29 | status: TStatus, 30 | Message: typeof DefaultMessage = DefaultMessage, 31 | ): ComponentType { 32 | return function WrappedWithStatusGuard(props: TProps) { 33 | const walletState = WalletState.useContainer() 34 | 35 | if (!isInStatus(walletState, status)) { 36 | return 37 | } 38 | 39 | return 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/config/__mocks__/renderer.ts: -------------------------------------------------------------------------------- 1 | import {Config} from '../type' 2 | 3 | export const config: Config = { 4 | rpcAddress: new URL('localhost:1234'), 5 | mantis: { 6 | dataDir: null, 7 | }, 8 | } as Config 9 | 10 | export const loadConfig = (): Config => config 11 | 12 | export const loadMantisWalletStatus = (): MantisWalletStatus => ({ 13 | node: { 14 | status: 'notRunning', 15 | }, 16 | info: { 17 | platform: 'Linux', 18 | platformVersion: 'Linux X', 19 | cpu: 'Intel', 20 | memory: 16000000, 21 | 22 | mantisWalletVersion: '0.11.0', 23 | mainPid: 1234, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /src/config/renderer.ts: -------------------------------------------------------------------------------- 1 | import {remote} from 'electron' 2 | import {pipe} from 'fp-ts/lib/pipeable' 3 | import _ from 'lodash/fp' 4 | import {Config} from './type' 5 | 6 | export const loadConfig = (): Config => 7 | pipe(remote.getGlobal('mantisWalletConfig'), (cfg) => ({ 8 | ...cfg, 9 | rpcAddress: new URL(cfg.rpcAddress), 10 | })) 11 | 12 | export const loadMantisWalletStatus = (): MantisWalletStatus => 13 | _.clone(remote.getGlobal('mantisWalletStatus')) 14 | 15 | // static config 16 | export const config: Config = loadConfig() 17 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss' 2 | -------------------------------------------------------------------------------- /src/external-link-config.ts: -------------------------------------------------------------------------------- 1 | import {DefinedNetworkName} from './config/type' 2 | 3 | export const LINKS = { 4 | support: 'https://iohk.zendesk.com/hc/en-us/requests/new', 5 | } 6 | 7 | type ExplorerLinks = Record string> 8 | 9 | export const EXPLORER_LINKS_FOR_TX: ExplorerLinks = { 10 | 'etc': (txHash) => `https://blockexplorer.one/etc/mainnet/tx/${txHash}`, 11 | 'mordor': (txHash) => `https://blockexplorer.one/etc/mordor/tx/${txHash}`, 12 | 'testnet-internal-nomad': (txHash) => 13 | `https://mantis-testnet-explorer.mantis.ws/transaction/${txHash}`, 14 | } 15 | -------------------------------------------------------------------------------- /src/functions.scss: -------------------------------------------------------------------------------- 1 | @function repeat-with-separator($string, $times, $separator: ' ') { 2 | @if $times <= 0 { 3 | @return ''; 4 | } 5 | 6 | $result: $string; 7 | 8 | @for $i from 2 through $times { 9 | $result: $result + $separator + $string; 10 | } 11 | 12 | @return $result; 13 | } 14 | 15 | // Used for ANTD overrides 16 | @mixin app-scoped() { 17 | #App, 18 | .ant-modal-root { 19 | @content; 20 | } 21 | } 22 | 23 | // Used for Dialog[...] components 24 | @mixin dialog-scoped() { 25 | #App, 26 | .storybook-modal-root, // only for storybook 27 | .ant-modal-root { 28 | @content; 29 | } 30 | } 31 | 32 | $height-breakpoint: 900px; 33 | 34 | @mixin header-margin-top() { 35 | @media only screen and (max-height: $height-breakpoint) { 36 | margin-top: 4rem; 37 | } 38 | @media only screen and (min-height: $height-breakpoint) { 39 | margin-top: 15vh; 40 | } 41 | } 42 | 43 | @mixin address-book-padding-top() { 44 | @media only screen and (max-height: $height-breakpoint) { 45 | padding-top: 4rem; 46 | } 47 | @media only screen and (min-height: $height-breakpoint) { 48 | padding-top: 15vh; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import './index.scss' 5 | 6 | ReactDOM.render(, document.getElementById('root')) 7 | -------------------------------------------------------------------------------- /src/jest.config.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log' 2 | import {copyToClipboard} from './common/clipboard' 3 | 4 | const mockedLogger = (): log.ElectronLog => { 5 | const mockedLogger = log.create('test-logger') 6 | 7 | // eslint-disable-next-line fp/no-mutation 8 | mockedLogger.transports.file.level = false 9 | // eslint-disable-next-line fp/no-mutation 10 | mockedLogger.transports.console.level = false 11 | 12 | return mockedLogger 13 | } 14 | 15 | jest.mock('./common/clipboard', () => ({copyToClipboard: jest.fn()})) 16 | jest.mock('./common/logger', () => ({rendererLog: mockedLogger()})) 17 | jest.mock('./main/logger', () => ({createMainLog: () => mockedLogger()})) 18 | jest.mock('./config/renderer.ts') 19 | jest.mock('./common/ipc-util.ts') 20 | 21 | // Workaround suggested by the official manual 22 | // https://jestjs.io/docs/en/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom 23 | // eslint-disable-next-line fp/no-mutating-methods 24 | Object.defineProperty(global, 'matchMedia', { 25 | writable: true, 26 | value: jest.fn().mockImplementation((query) => ({ 27 | matches: false, 28 | media: query, 29 | onchange: null, 30 | addListener: jest.fn(), // deprecated 31 | removeListener: jest.fn(), // deprecated 32 | addEventListener: jest.fn(), 33 | removeEventListener: jest.fn(), 34 | dispatchEvent: jest.fn(), 35 | })), 36 | }) 37 | 38 | export const mockedCopyToClipboard = copyToClipboard as unknown 39 | 40 | afterEach(() => { 41 | ;(mockedCopyToClipboard as ReturnType).mockClear() 42 | }) 43 | 44 | export {} 45 | -------------------------------------------------------------------------------- /src/layout/Router.scss: -------------------------------------------------------------------------------- 1 | @import '../sass-includes'; 2 | 3 | .Router { 4 | position: relative; 5 | height: 100vh; 6 | padding: $app-padding-vertical $app-padding-horizontal; 7 | overflow: auto; 8 | } 9 | -------------------------------------------------------------------------------- /src/layout/Router.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useRef, Suspense} from 'react' 2 | import ErrorBoundary from 'antd/lib/alert/ErrorBoundary' 3 | import {RouterState} from '../router-state' 4 | import {RouteId} from '../routes-config' 5 | import './Router.scss' 6 | 7 | export const Router = (): JSX.Element => { 8 | const { 9 | currentRoute: {component: Component}, 10 | } = RouterState.useContainer() 11 | const mainRef = useRef(null) 12 | 13 | useEffect(() => { 14 | mainRef.current?.scroll({top: 0}) 15 | }, [Component]) 16 | 17 | return ( 18 |
19 | 20 | }> 21 | 22 | 23 | 24 |
25 | ) 26 | } 27 | 28 | interface NavigateProps { 29 | to: RouteId 30 | } 31 | 32 | export const Navigate = ({to}: NavigateProps): JSX.Element => { 33 | const routerState = RouterState.useContainer() 34 | 35 | useEffect(() => { 36 | routerState.navigate(to) 37 | }, []) 38 | 39 | return <> 40 | } 41 | -------------------------------------------------------------------------------- /src/layout/Sidebar.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {action} from '@storybook/addon-actions' 3 | import {ESSENTIAL_DECORATORS} from '../storybook-util/essential-decorators' 4 | import {RouterState} from '../router-state' 5 | import {Sidebar} from './Sidebar' 6 | import {RemoveWalletModal} from '../wallets/modals/RemoveWalletModal' 7 | import {asyncAction} from '../storybook-util/custom-knobs' 8 | import {toFullScreen} from '../storybook-util/full-screen-decorator' 9 | import {withTokensState} from '../storybook-util/tokens-state-decorator' 10 | 11 | export default { 12 | title: 'Sidebar', 13 | decorators: [...ESSENTIAL_DECORATORS, withTokensState, toFullScreen], 14 | } 15 | 16 | export const sidebar = (): JSX.Element => ( 17 | 18 | 19 | 20 | ) 21 | 22 | export const removeWalletModal = (): JSX.Element => ( 23 | 28 | ) 29 | -------------------------------------------------------------------------------- /src/main/log-exporter.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import archiver from 'archiver' 4 | import {Config} from '../config/type' 5 | import {status} from './status' 6 | import {MainStore} from './store' 7 | import {CheckedDatadir, getMantisDatadirPath} from './data-dir' 8 | 9 | export const createLogExporter = (_checkedDatadir: CheckedDatadir) => async ( 10 | config: Config, 11 | store: MainStore, 12 | rendererStoreData: string, 13 | outputFilePath: string, 14 | ): Promise => { 15 | // Create and save archive 16 | 17 | const archive = archiver('zip', { 18 | zlib: {level: 9}, 19 | }) 20 | 21 | const mantisDatadirPath = getMantisDatadirPath(config, store) 22 | 23 | const backendLogPath = path.join(mantisDatadirPath, store.get('networkName'), 'logs') 24 | const walletLogPath = path.join(config.walletDataDir, 'logs') 25 | const configAndStatusText = JSON.stringify({config, status}, null, 2) 26 | const rendererStore = JSON.stringify(JSON.parse(rendererStoreData), null, 2) 27 | 28 | archive 29 | .directory(backendLogPath, false) 30 | .directory(walletLogPath, false) 31 | .append(configAndStatusText, {name: 'wallet-config-and-status.json'}) 32 | .append(rendererStore, {name: 'renderer-store.json'}) 33 | 34 | const output = fs.createWriteStream(outputFilePath) 35 | archive.pipe(output) 36 | await archive.finalize() 37 | } 38 | -------------------------------------------------------------------------------- /src/main/logger.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import _ from 'lodash/fp' 3 | import {ElectronLog} from 'electron-log' 4 | import {createLogger} from '../shared/utils' 5 | import {Config} from '../config/type' 6 | import {CheckedDatadir, getMantisDatadirPath} from './data-dir' 7 | import {MainStore} from './store' 8 | 9 | export const createMainLog = ( 10 | _checkedDatadir: CheckedDatadir, 11 | store: MainStore, 12 | config: Config, 13 | ): ElectronLog => { 14 | const mainLog = createLogger('main', () => 15 | path.join(getMantisDatadirPath(config, store), store.get('networkName'), 'logs', 'main.log'), 16 | ) 17 | 18 | // eslint-disable-next-line fp/no-mutating-methods 19 | mainLog.hooks.push((message, transport) => { 20 | if (transport !== mainLog.transports.console) { 21 | return message 22 | } 23 | 24 | return { 25 | ...message, 26 | data: message.data.map((v) => (_.isPlainObject(v) ? JSON.stringify(v, null, 2) : v)), 27 | } 28 | }) 29 | 30 | return mainLog 31 | } 32 | -------------------------------------------------------------------------------- /src/main/port-usage-check.ts: -------------------------------------------------------------------------------- 1 | import {getPortPromise} from 'portfinder' 2 | import {createTErrorMain} from './i18n' 3 | import {Config} from '../config/type' 4 | 5 | const getBlockedPorts = (requiredPorts: number[]): Promise => { 6 | return Promise.all( 7 | requiredPorts.map( 8 | (port: number): Promise => 9 | getPortPromise({port, stopPort: port}) 10 | .then(() => []) 11 | .catch(() => [port]), 12 | ), 13 | ).then((r) => r.flat()) 14 | } 15 | 16 | export async function checkPortUsage(config: Config): Promise { 17 | const requiredPorts = [parseInt(config.rpcAddress.port)] 18 | const blockedPorts = await getBlockedPorts(requiredPorts) 19 | if (blockedPorts.length > 0) { 20 | const ports = blockedPorts.join(', ') 21 | throw createTErrorMain(['error', 'portsAlreadyInUse'], { 22 | replace: {ports}, 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/status.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable fp/no-mutation */ 2 | import os from 'os' 3 | import {MANTIS_WALLET_VERSION} from '../shared/version' 4 | 5 | export const status: MantisWalletStatus = { 6 | node: { 7 | status: 'notRunning', 8 | }, 9 | info: { 10 | platform: os.type(), 11 | platformVersion: os.release(), 12 | cpu: os.cpus()[0].model, 13 | memory: os.totalmem(), 14 | 15 | mantisWalletVersion: MANTIS_WALLET_VERSION, 16 | mainPid: process.pid, 17 | }, 18 | } 19 | 20 | export function setMantisStatus(processInfo: {pid?: number; status: ProcessStatus}): void { 21 | status.node = processInfo 22 | } 23 | -------------------------------------------------------------------------------- /src/main/store.ts: -------------------------------------------------------------------------------- 1 | import ElectronStore from 'electron-store' 2 | import {NetworkName} from '../config/type' 3 | import {Language, DEFAULT_LANGUAGE} from '../shared/i18n' 4 | import {emitToRenderer} from './util' 5 | 6 | type StoreData = { 7 | 'settings.language': Language 8 | 'settings.mantisDatadir': string 9 | 'networkName': NetworkName 10 | } 11 | 12 | export type MainStore = ElectronStore 13 | 14 | export const createStore = ( 15 | walletDatadirPath: string, 16 | mantisDatadirPath: string | null, 17 | networkName: NetworkName, 18 | ): MainStore => { 19 | const DEFAULT_DATA: StoreData = { 20 | 'settings.language': DEFAULT_LANGUAGE, 21 | 'settings.mantisDatadir': mantisDatadirPath ?? '', 22 | 'networkName': networkName, 23 | } 24 | 25 | const store = new ElectronStore({ 26 | cwd: walletDatadirPath, 27 | defaults: DEFAULT_DATA, 28 | watch: true, 29 | }) 30 | 31 | store.onDidAnyChange(() => { 32 | emitToRenderer('store-changed') 33 | }) 34 | 35 | return store 36 | } 37 | -------------------------------------------------------------------------------- /src/main/streamUtils.ts: -------------------------------------------------------------------------------- 1 | import {Readable} from 'stream' 2 | import {Observable} from 'rxjs' 3 | 4 | export const readableToObservable = (readableStream: Readable): Observable => 5 | new Observable((subscriber) => { 6 | readableStream.on('data', (chunk) => subscriber.next(chunk)) 7 | readableStream.on('error', (err) => subscriber.error(err)) 8 | readableStream.on('end', () => subscriber.complete()) 9 | readableStream.on('close', () => subscriber.complete()) 10 | }) 11 | -------------------------------------------------------------------------------- /src/main/test/data/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo `dirname $0` 4 | cd `dirname $0` 5 | 6 | rm ./mantisCA.p12 ./mantisCA.pem ./params.json ./password 7 | 8 | export PW=`pwgen -Bs 10 1` 9 | echo $PW > ./password 10 | 11 | ISSUER=mantis-wallet-`pwgen -Bs 10 1` 12 | DOMAIN=mantis-wallet-`pwgen -Bs 10 1` 13 | IP="127.0.0.$((1 + $RANDOM % 255))" 14 | 15 | keytool -genkeypair \ 16 | -keystore mantisCA.p12 \ 17 | -storetype PKCS12 \ 18 | -dname "CN=$ISSUER" \ 19 | -ext "san=ip:$IP,dns:$DOMAIN" \ 20 | -keypass:env PW \ 21 | -storepass:env PW \ 22 | -keyalg RSA \ 23 | -keysize 4096 \ 24 | -validity 9999 \ 25 | -ext KeyUsage:critical="keyCertSign" \ 26 | -ext BasicConstraints:critical="ca:true" 27 | 28 | openssl pkcs12 -in mantisCA.p12 -passin pass:${PW} -nokeys -out mantisCA.pem 29 | 30 | FINGERPRINT=`openssl x509 -in mantisCA.pem -noout -sha256 -fingerprint | cut -d'=' -f2` 31 | 32 | SERIAL=`openssl x509 -in mantisCA.pem -noout -serial | cut -d'=' -f2` 33 | 34 | PARAMS="{ 35 | \"password\": \"$PW\", \ 36 | \"issuer\": \"$ISSUER\", \ 37 | \"domain\": \"$DOMAIN\", \ 38 | \"ip\": \"$IP\", \ 39 | \"fingerprint\": \"$FINGERPRINT\", \ 40 | \"serialNumber\": \"$SERIAL\" \ 41 | }" 42 | echo $PARAMS > ./params.json 43 | echo $PARAMS 44 | -------------------------------------------------------------------------------- /src/main/test/data/mantisCA.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/mantis-wallet/2da51e9696a2bf5eb8f51f4b8408159ffd89c32b/src/main/test/data/mantisCA.p12 -------------------------------------------------------------------------------- /src/main/test/data/params.json: -------------------------------------------------------------------------------- 1 | { "password": "wPNUAwfM9T", "issuer": "mantis-wallet-FsfK9NevWj", "domain": "mantis-wallet-3rgo7RakrT", "ip": "127.0.0.20", "fingerprint": "54:45:7E:52:9D:B0:D9:90:C4:7E:8F:3A:BE:69:B9:E4:7E:3F:C5:5C:E1:AA:3F:8B:02:F0:A2:1E:41:78:DF:18", "serialNumber": "3F26CA5C" } 2 | -------------------------------------------------------------------------------- /src/main/test/data/password: -------------------------------------------------------------------------------- 1 | wPNUAwfM9T 2 | -------------------------------------------------------------------------------- /src/main/types.d.ts: -------------------------------------------------------------------------------- 1 | type ProcessStatus = 'notRunning' | 'running' | 'finished' | 'failed' 2 | 3 | interface MantisWalletStatus { 4 | node: { 5 | pid?: number 6 | status: ProcessStatus 7 | } 8 | info: { 9 | platform: string 10 | platformVersion: string 11 | cpu: string 12 | memory: number 13 | 14 | mantisWalletVersion: string 15 | mainPid: number 16 | mantisVersion?: string 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | // Typed wrapper for listening to IPC events 2 | import {promises as fs} from 'fs' 3 | import {app, ipcMain, dialog} from 'electron' 4 | import {IPCFromRendererChannelName, IPCToRendererChannelName} from '../shared/ipc-types' 5 | import {EDITION, MANTIS_WALLET_VERSION} from '../shared/version' 6 | import {TFunctionMain} from './i18n' 7 | import {displayNameOfNetworkMain} from '../config/type' 8 | 9 | export function ipcListenToRenderer( 10 | channel: IPCFromRendererChannelName, 11 | listener: Parameters[1], 12 | ): void { 13 | ipcMain.on(channel, listener) 14 | } 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | export function emitToRenderer(channel: IPCToRendererChannelName, ...args: any[]): void { 18 | ipcMain.emit(channel, ...args) 19 | } 20 | 21 | export function getTitle(t: TFunctionMain, networkType?: string): string { 22 | const networkName = displayNameOfNetworkMain(networkType || '', t) 23 | return `${t(['title', 'mantisWallet'])} — ${MANTIS_WALLET_VERSION} ${EDITION} — ${networkName}` 24 | } 25 | 26 | export function showErrorBox(t: TFunctionMain, title: string, content: string): void { 27 | if (app.isReady()) { 28 | dialog.showMessageBoxSync({ 29 | type: 'error', 30 | buttons: [t(['dialog', 'button', 'ok'])], 31 | title: t(['dialog', 'title', 'error']), 32 | message: title, 33 | detail: content, 34 | }) 35 | } else { 36 | dialog.showErrorBox(title, content) 37 | } 38 | } 39 | 40 | export const isDirEmpty = (dir: string): Promise => 41 | fs.readdir(dir).then((files) => files.length === 0) 42 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/router-state.ts: -------------------------------------------------------------------------------- 1 | import {useState} from 'react' 2 | import {createContainer} from 'unstated-next' 3 | import {ROUTES, MENU, RouteId, Route, MenuItem} from './routes-config' 4 | import {rendererLog} from './common/logger' 5 | 6 | interface RouterState { 7 | currentRouteId: RouteId 8 | currentRoute: Route 9 | currentMenu: MenuItem 10 | isLocked: boolean 11 | navigate: (routeId: RouteId) => void 12 | setLocked: (isLocked: boolean) => void 13 | } 14 | 15 | interface InitialState { 16 | routeId: RouteId 17 | isLocked: boolean 18 | } 19 | 20 | function useRouterState( 21 | initialState: InitialState = {routeId: 'TXNS', isLocked: false}, 22 | ): RouterState { 23 | const [currentRouteId, setCurrentRouteId] = useState(initialState.routeId) 24 | const [isLocked, setLocked] = useState(initialState.isLocked) 25 | 26 | const currentRoute = ROUTES[currentRouteId] 27 | const currentMenu = MENU[currentRoute.menu] 28 | 29 | const navigate = (routeId: RouteId): void => { 30 | if (isLocked) { 31 | return rendererLog.debug(`Attempted navigation to ${routeId} while navigation is locked`) 32 | } 33 | if (routeId === currentRouteId) { 34 | return rendererLog.debug(`Attempted double navigation to ${routeId}`) 35 | } 36 | rendererLog.debug(`Navigation to ${routeId}`) 37 | setCurrentRouteId(routeId) 38 | } 39 | 40 | return { 41 | currentRouteId, 42 | currentRoute, 43 | currentMenu, 44 | isLocked, 45 | navigate, 46 | setLocked, 47 | } 48 | } 49 | 50 | export const RouterState = createContainer(useRouterState) 51 | -------------------------------------------------------------------------------- /src/sass-includes.scss: -------------------------------------------------------------------------------- 1 | @import './functions', './vars', './themify', './partial'; 2 | // This file doesn't include any "rendered" styles, only functions/partials/vars, 3 | // so it's safe to import anywhere. 4 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | 3 | // eslint-disable-next-line fp/no-mutation 4 | global.matchMedia = function (query: string) { 5 | return { 6 | matches: false, 7 | media: query, 8 | onchange: null, 9 | addListener: jest.fn(), // deprecated 10 | removeListener: jest.fn(), // deprecated 11 | addEventListener: jest.fn(), 12 | removeEventListener: jest.fn(), 13 | dispatchEvent: jest.fn(), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/shared/ArrayOps.ts: -------------------------------------------------------------------------------- 1 | export const concat = (a: readonly T[]) => (b: readonly T[]): readonly T[] => a.concat(b) 2 | -------------------------------------------------------------------------------- /src/shared/OptionOps.ts: -------------------------------------------------------------------------------- 1 | import {option} from 'fp-ts' 2 | import {Option} from 'fp-ts/lib/Option' 3 | import {pipe} from 'fp-ts/pipeable' 4 | 5 | export const toArray = (optionA: Option): readonly A[] => 6 | option.fold( 7 | () => [], 8 | (a: A) => [a], 9 | )(optionA) 10 | 11 | export const zip = (maybeA: Option) => (maybeB: Option): Option<[A, B]> => 12 | pipe( 13 | maybeA, 14 | option.chain((a) => 15 | pipe( 16 | maybeB, 17 | option.map((b) => [a, b]), 18 | ), 19 | ), 20 | ) 21 | -------------------------------------------------------------------------------- /src/shared/RxOps.ts: -------------------------------------------------------------------------------- 1 | import * as rx from 'rxjs' 2 | import * as rxop from 'rxjs/operators' 3 | 4 | export const tapEval = (evalCb: (a: A) => Promise): rx.OperatorFunction => 5 | rxop.concatMap((a: A) => evalCb(a).then(() => a)) 6 | -------------------------------------------------------------------------------- /src/shared/SetOps.test.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check' 2 | import {eq, set} from 'fp-ts' 3 | import {intersectionAndDiffs} from './SetOps' 4 | 5 | describe('SetOps', () => { 6 | describe('intersectionsAndDiffs', () => { 7 | it('calculates sets for given A and B: A\\B, B\\A, A⋂B', () => { 8 | fc.assert( 9 | fc.property( 10 | fc.set(fc.string()).map((s) => new Set(s)), 11 | fc.set(fc.string()).map((s) => new Set(s)), 12 | (a, b) => { 13 | const {aOnly, bOnly, common} = intersectionAndDiffs(eq.eqString, a, b) 14 | 15 | expect(aOnly).toEqual(set.difference(eq.eqString)(a, b)) 16 | expect(bOnly).toEqual(set.difference(eq.eqString)(b, a)) 17 | expect(common).toEqual(set.intersection(eq.eqString)(a, b)) 18 | }, 19 | ), 20 | ) 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/shared/SetOps.ts: -------------------------------------------------------------------------------- 1 | import {Eq} from 'fp-ts/Eq' 2 | import {set} from 'fp-ts' 3 | 4 | export const intersectionAndDiffs = ( 5 | eq: Eq, 6 | a: Set, 7 | b: Set, 8 | ): {aOnly: Set; common: Set; bOnly: Set} => { 9 | const aOnly = new Set() 10 | const bOnly = new Set() 11 | const common = new Set() 12 | 13 | a.forEach((aItem) => { 14 | if (set.elem(eq)(aItem, b)) { 15 | common.add(aItem) 16 | } else { 17 | aOnly.add(aItem) 18 | } 19 | }) 20 | b.forEach((bItem) => { 21 | if (!set.elem(eq)(bItem, common)) { 22 | bOnly.add(bItem) 23 | } 24 | }) 25 | 26 | return {aOnly, bOnly, common} 27 | } 28 | -------------------------------------------------------------------------------- /src/shared/extendable-error.ts: -------------------------------------------------------------------------------- 1 | export abstract class ExtendableError extends Error { 2 | name: string 3 | 4 | constructor(public message = '') { 5 | super(message) 6 | this.name = new.target.name // eslint-disable-line 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/shared/i18n.ts: -------------------------------------------------------------------------------- 1 | import {TOptions, TFunctionResult, StringMap} from 'i18next' 2 | import {ExtendableError} from './extendable-error' 3 | import {Path} from './typeUtils' 4 | 5 | export const DEFAULT_LANGUAGE = 'en' 6 | export const DEFAULT_NAMESPACE = 'translations' 7 | 8 | // LANGUAGES should be using BCP-47 language tag format 9 | export const LANGUAGES = [DEFAULT_LANGUAGE] as const 10 | export type Language = typeof LANGUAGES[number] 11 | export type Namespace = typeof DEFAULT_NAMESPACE 12 | 13 | export interface TypedTFunction { 14 | ( 15 | key: Path, 16 | options?: TOptions | string, 17 | ): TResult 18 | } 19 | 20 | export class GenericTError extends ExtendableError { 21 | tKey: Path 22 | options?: TOptions | string 23 | 24 | constructor(message: string, tKey: Path, options?: TOptions | string) { 25 | super(message) 26 | this.tKey = tKey // eslint-disable-line 27 | this.options = options // eslint-disable-line 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | import * as ArrayOps from './ArrayOps' 2 | import * as SetOps from './SetOps' 3 | import * as OptionOps from './OptionOps' 4 | import * as RxOps from './RxOps' 5 | 6 | export {ArrayOps, SetOps, OptionOps, RxOps} 7 | -------------------------------------------------------------------------------- /src/shared/ipc-types.ts: -------------------------------------------------------------------------------- 1 | export type IPCToRendererChannelName = 2 | | 'update-config-success' 3 | | 'update-config-failure' 4 | | 'save-debug-logs-success' 5 | | 'save-debug-logs-failure' 6 | | 'save-debug-logs-cancel' 7 | | 'store-changed' 8 | 9 | export type IPCFromRendererChannelName = 10 | | 'update-network-type' 11 | | 'save-debug-logs' 12 | | 'update-language' 13 | | 'update-network-name' 14 | | 'update-datadir-location' 15 | -------------------------------------------------------------------------------- /src/shared/typeUtils.ts: -------------------------------------------------------------------------------- 1 | export type Diff = T extends U ? never : T //Remove types from T that are assignable to U 2 | export type Filter = T extends U ? T : never // Remove types from T that are not assignable to U 3 | 4 | // ==== Types for keys of object 5 | // see https://stackoverflow.com/a/58436959 6 | type Cons = T extends readonly any[] // eslint-disable-line @typescript-eslint/no-explicit-any 7 | ? ((h: H, ...t: T) => void) extends (...r: infer R) => void 8 | ? R 9 | : never 10 | : never 11 | 12 | type Prev = [ 13 | never, 14 | 0, 15 | 1, 16 | 2, 17 | 3, 18 | 4, 19 | 5, 20 | 6, 21 | 7, 22 | 8, 23 | 9, 24 | 10, 25 | 11, 26 | 12, 27 | 13, 28 | 14, 29 | 15, 30 | 16, 31 | 17, 32 | 18, 33 | 19, 34 | 20, 35 | ...0[] // eslint-disable-line @typescript-eslint/array-type 36 | ] 37 | 38 | /** 39 | * Use Path to type-safe path in an object to its last nodes 40 | * 41 | * const obj = {foo: {bar: 'string'}} 42 | * 43 | * const pathToBar: Path = ['foo', 'bar'] // correct 44 | * const pathToFoo: Path = ['foo'] // error 45 | */ 46 | export type Path = [D] extends [never] 47 | ? never 48 | : T extends object 49 | ? {[K in keyof T]-?: Cons>}[keyof T] 50 | : [] 51 | // ==== Types for keys of object 52 | -------------------------------------------------------------------------------- /src/shared/version.ts: -------------------------------------------------------------------------------- 1 | import {version, compatibleDataDirVersions} from '../../package.json' 2 | 3 | export const MANTIS_WALLET_VERSION = `v${version}` 4 | export const EDITION = 'BETA' 5 | 6 | export const DATADIR_VERSION = version 7 | export const COMPATIBLE_VERSIONS = compatibleDataDirVersions 8 | -------------------------------------------------------------------------------- /src/storybook-util/backend-state-decorator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {makeDecorator, StoryContext, StoryGetter, StoryWrapper} from '@storybook/addons' 3 | import {BackendState} from '../common/backend-state' 4 | 5 | const WithBackendState: StoryWrapper = ( 6 | storyFn: StoryGetter, 7 | context: StoryContext, 8 | ): JSX.Element => { 9 | const content = storyFn(context) 10 | return {content} 11 | } 12 | 13 | export const withBackendState = makeDecorator({ 14 | name: 'withBackendState', 15 | parameterName: 'withBackendState', 16 | wrapper: WithBackendState, 17 | }) 18 | -------------------------------------------------------------------------------- /src/storybook-util/custom-knobs.ts: -------------------------------------------------------------------------------- 1 | import {number} from '@storybook/addon-knobs' 2 | import {action, ActionOptions} from '@storybook/addon-actions' 3 | import {Wei, asEther} from '../common/units' 4 | 5 | export const ether = (name: string, value: number): Wei => asEther(number(name, value)) 6 | 7 | export const asyncAction = ( 8 | name: string, 9 | delay = 1000, 10 | options?: ActionOptions | undefined, 11 | // eslint-disable-next-line 12 | ): ((...args: any[]) => Promise) => { 13 | return (...args) => 14 | new Promise(function (resolve) { 15 | setTimeout(resolve, delay) 16 | }).then(() => action(name, options)(args)) 17 | } 18 | -------------------------------------------------------------------------------- /src/storybook-util/essential-decorators.ts: -------------------------------------------------------------------------------- 1 | import {withKnobs} from '@storybook/addon-knobs' 2 | import {withSettings} from './settings-state-decorator' 3 | import {withBackendState} from './backend-state-decorator' 4 | import {withWalletState} from './wallet-state-decorator' 5 | import {withRouterState} from './router-state-decorator' 6 | 7 | export const ESSENTIAL_DECORATORS = [ 8 | withSettings, 9 | withBackendState, 10 | withWalletState, 11 | withRouterState, 12 | withKnobs, 13 | ] as const 14 | -------------------------------------------------------------------------------- /src/storybook-util/full-screen-decorator.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react' 2 | import {makeDecorator, StoryContext, StoryGetter} from '@storybook/addons' 3 | 4 | const ToFullScreen = (storyFn: StoryGetter, context: StoryContext): JSX.Element => { 5 | useEffect(() => { 6 | document.getElementById('main')?.classList.add('no-padding') 7 | }, []) 8 | 9 | return <>{storyFn(context)} 10 | } 11 | 12 | export const toFullScreen = makeDecorator({ 13 | name: 'toFullScreen', 14 | parameterName: 'fullScreen', 15 | wrapper: ToFullScreen, 16 | }) 17 | -------------------------------------------------------------------------------- /src/storybook-util/router-state-decorator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {makeDecorator, StoryContext, StoryGetter} from '@storybook/addons' 3 | import {RouterState} from '../router-state' 4 | 5 | const WithRouterState = (storyFn: StoryGetter, context: StoryContext): JSX.Element => { 6 | const content = storyFn(context) 7 | return {content} 8 | } 9 | 10 | export const withRouterState = makeDecorator({ 11 | name: 'withRouterState', 12 | parameterName: 'routerState', 13 | wrapper: WithRouterState, 14 | }) 15 | -------------------------------------------------------------------------------- /src/storybook-util/shared-constants.ts: -------------------------------------------------------------------------------- 1 | const ADDON_PREFIX = 'mantis-wallet' 2 | 3 | export const THEME_SWITCHER_ADDON_ID = `${ADDON_PREFIX}/theme-switcher` 4 | export const THEME_SWITCHER_CHANGE = `${THEME_SWITCHER_ADDON_ID}/change` 5 | 6 | export const NETWORK_SWITCHER_ADDON_ID = `${ADDON_PREFIX}/network-switcher` 7 | export const NETWORK_SWITCHER_CHANGE = `${NETWORK_SWITCHER_ADDON_ID}/change` 8 | 9 | export const LANGUAGE_CHANGER_ADDON_ID = `${ADDON_PREFIX}/language-changer` 10 | export const LANGUAGE_CHANGER_PSEUDO_SWITCH = `${LANGUAGE_CHANGER_ADDON_ID}/pseudo-switch` 11 | -------------------------------------------------------------------------------- /src/storybook-util/tokens-state-decorator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {makeDecorator, StoryContext, StoryGetter} from '@storybook/addons' 3 | import {TokensState} from '../tokens/tokens-state' 4 | 5 | const WithTokensState = (storyFn: StoryGetter, context: StoryContext): JSX.Element => { 6 | const content = storyFn(context) 7 | return {content} 8 | } 9 | 10 | export const withTokensState = makeDecorator({ 11 | name: 'withTokensState', 12 | parameterName: 'tokensState', 13 | wrapper: WithTokensState, 14 | }) 15 | -------------------------------------------------------------------------------- /src/themify.scss: -------------------------------------------------------------------------------- 1 | @import 'themes'; 2 | 3 | $theme-map: null; 4 | 5 | @mixin themify($themes: $themes) { 6 | // iterate over every defined theme 7 | @each $theme, $map in $themes { 8 | #Mantis.theme-#{$theme} & { 9 | // create global $theme-map to use in the scope of themed() 10 | $theme-map: () !global; 11 | @each $key, $submap in $map { 12 | $value: map-get(map-get($themes, $theme), '#{$key}'); 13 | $theme-map: map-merge( 14 | $theme-map, 15 | ( 16 | $key: $value, 17 | ) 18 | ) !global; 19 | } 20 | 21 | // output themed content 22 | @content; 23 | 24 | // unset $theme-map to make it inaccessible in outer styles 25 | $theme-map: null !global; 26 | } 27 | } 28 | } 29 | 30 | @function themed($key) { 31 | // throw error if $key is not defined in the theme 32 | @if not map-has-key($theme-map, $key) { 33 | @error "Unknown themed variable " + $key; 34 | } 35 | 36 | @return map-get($theme-map, $key); 37 | } 38 | -------------------------------------------------------------------------------- /src/tokens/Tokens.scss: -------------------------------------------------------------------------------- 1 | @import '../vars'; 2 | 3 | .Tokens { 4 | .content { 5 | padding: $main-content-padding 0; 6 | 7 | .toolbar { 8 | text-align: right; 9 | 10 | .action { 11 | display: inline-block; 12 | height: 2rem; 13 | margin-right: 1em; 14 | padding: 0 2em; 15 | font-size: $font-size-s; 16 | font-weight: bold; 17 | line-height: 2rem; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/tokens/modals/HideTokenModal.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent} from 'react' 2 | import {wrapWithModal, ModalOnCancel} from '../../common/MantisModal' 3 | import {Dialog} from '../../common/Dialog' 4 | import {Token} from '../tokens-state' 5 | import {DialogMessage} from '../../common/dialog/DialogMessage' 6 | import {Trans} from '../../common/Trans' 7 | 8 | interface HideTokenModalProps extends ModalOnCancel { 9 | onHideToken: (token: Token) => void 10 | token: Token 11 | } 12 | 13 | const HideTokenDialog: FunctionComponent = ({ 14 | onHideToken, 15 | token, 16 | onCancel, 17 | }: HideTokenModalProps) => ( 18 | } 20 | leftButtonProps={{ 21 | onClick: onCancel, 22 | }} 23 | rightButtonProps={{ 24 | children: , 25 | onClick: () => onHideToken(token), 26 | }} 27 | > 28 | 29 | 30 | 31 | 32 | ) 33 | 34 | export const HideTokenModal = wrapWithModal(HideTokenDialog) 35 | -------------------------------------------------------------------------------- /src/tokens/modals/ReceiveTokenModal.scss: -------------------------------------------------------------------------------- 1 | @import '../../partial', '../../vars'; 2 | 3 | .ReceiveTokenModal { 4 | .mode-switch { 5 | width: 172px; 6 | margin-bottom: 1rem; 7 | } 8 | 9 | .footer { 10 | .title { 11 | padding: 0 0 0.5rem; 12 | opacity: $secondary-opacity; 13 | font-size: $font-size-s; 14 | font-weight: bold; 15 | text-transform: uppercase; 16 | } 17 | 18 | .account { 19 | width: 508px; 20 | padding: 0.5rem 0; 21 | overflow: hidden; 22 | opacity: $secondary-opacity; 23 | font-size: $font-size-s; 24 | 25 | &:hover { 26 | opacity: 1; 27 | } 28 | 29 | .address { 30 | display: inline; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/tokens/tokens-utils.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import {TransactionConfig} from 'web3-core' 3 | import {Token} from './tokens-state' 4 | import {Formatters} from '../settings-state' 5 | 6 | export function getSendTokenParams( 7 | token: Token, 8 | senderAddress: string, 9 | recipientAddress: string, 10 | amount: BigNumber, 11 | ): TransactionConfig { 12 | const data = `fake data ${recipientAddress} ${amount.toString(10)}` //FIXME ETCM-115 13 | 14 | return { 15 | from: senderAddress, 16 | to: token.address, 17 | value: '0', 18 | data, 19 | } 20 | } 21 | 22 | export const formatTokenAmount = ( 23 | abbreviateAmount: Formatters['abbreviateAmount'], 24 | token: Token, 25 | amount: BigNumber, 26 | ): string => `${abbreviateAmount(amount.shiftedBy(-token.decimals)).relaxed} ${token.symbol}` 27 | -------------------------------------------------------------------------------- /src/types/tests/index.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript Version: 3.8 2 | // see https://github.com/Microsoft/dtslint#specify-a-typescript-version for more information 3 | -------------------------------------------------------------------------------- /src/types/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | // this additional tsconfig is required by dtslint 2 | // see: https://github.com/Microsoft/dtslint#typestsconfigjson 3 | { 4 | "extends": "../../../tsconfig.json", 5 | "compilerOptions": { 6 | "lib": [ 7 | "dom", 8 | "dom.iterable", 9 | "esnext" 10 | ], 11 | "noImplicitAny": true, 12 | "noImplicitThis": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "noEmit": true 16 | }, 17 | "include": [ 18 | "." 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/types/tests/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["dtslint/dtslint.json"], 3 | "rules": { 4 | "semicolon": false, 5 | "no-useless-files": false, 6 | "no-relative-import-in-test": false, 7 | "whitespace": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/types/tests/typeUtils.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import {Path, Diff, Filter} from '../../shared/typeUtils' 3 | 4 | interface TestObj1Type { 5 | a: string 6 | b: { 7 | c: string 8 | } 9 | } 10 | interface TestObj2Type { 11 | a: string 12 | b: { 13 | c: string 14 | d: string 15 | } 16 | } 17 | 18 | // $ExpectType [h: "a"] | [h: "b", h: "c"] 19 | type TestPathForObj1 = Path 20 | 21 | // $ExpectType [h: "a"] | [h: "b", h: "c"] | [h: "b", h: "d"] 22 | type TestPathForObj2 = Path 23 | 24 | // $ExpectType TestObj1Type 25 | type TestDiffForObj1Obj2 = Diff 26 | 27 | // $ExpectType never 28 | type TestDiffForObj2Obj1 = Diff 29 | 30 | // $ExpectType never 31 | type TestFilterForObj1Obj2 = Filter 32 | 33 | // $ExpectType TestObj2Type 34 | type TestFilterForObj2Obj1 = Filter 35 | -------------------------------------------------------------------------------- /src/types/web3-eth.d.ts: -------------------------------------------------------------------------------- 1 | import 'web3-eth' 2 | 3 | declare module 'web3-eth' { 4 | export interface SyncingFixed { 5 | startingBlock: number 6 | currentBlock: number 7 | highestBlock: number 8 | knownStates: number 9 | pulledStates: number 10 | } 11 | interface Eth { 12 | isSyncing( 13 | callback?: (error: Error, syncing: SyncingFixed) => void, 14 | ): Promise 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/vars-for-ts.scss: -------------------------------------------------------------------------------- 1 | /* 2 | variables to use via typescript import 3 | e.g.: import * as styles from '../vars-for-ts.scss'; styles.successColor 4 | */ 5 | @import 'vars'; 6 | 7 | :export { 8 | successColor: $success-color; 9 | errorColor: $error-color; 10 | warningColor: $warning-color; 11 | } 12 | -------------------------------------------------------------------------------- /src/wallets/BalanceDisplay.scss: -------------------------------------------------------------------------------- 1 | @import '../themify', '../vars', '../partial'; 2 | 3 | .BalanceDisplay { 4 | margin: 0; 5 | 6 | .balances { 7 | .label { 8 | margin-bottom: 0.5vw; 9 | font-size: 0.8vw; 10 | font-weight: 600; 11 | } 12 | 13 | .available-balance { 14 | font-size: 5vw; 15 | font-weight: 400; 16 | letter-spacing: -0.2vw; 17 | line-height: 4vw; 18 | font-variant-numeric: tabular-nums; 19 | } 20 | 21 | .suffix { 22 | font-size: 1vw; 23 | font-weight: 600; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/wallets/BalanceDisplay.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect' 2 | import React from 'react' 3 | import {some} from 'fp-ts/lib/Option' 4 | import {render, waitFor} from '@testing-library/react' 5 | import {createWithProviders} from '../common/test-helpers' 6 | import {asEther} from '../common/units' 7 | import {BalanceDisplay} from './BalanceDisplay' 8 | 9 | it('shows properly formatted balance', async () => { 10 | const availableBalance = some(asEther(12345)) 11 | 12 | const {queryByText} = render(, { 13 | wrapper: createWithProviders(), 14 | }) 15 | 16 | await waitFor(() => expect(queryByText('12.35k')).toBeInTheDocument(), {timeout: 2500}) 17 | }) 18 | -------------------------------------------------------------------------------- /src/wallets/NoWallet.scss: -------------------------------------------------------------------------------- 1 | .NoWallet { 2 | display: grid; 3 | position: absolute; 4 | z-index: 0; 5 | top: 0; 6 | right: 0; 7 | grid-template-rows: 1fr; 8 | width: 100%; 9 | height: 100%; 10 | 11 | > .Dialog { 12 | width: 800px; 13 | 14 | .title { 15 | margin-left: 0; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/wallets/NoWallet.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent} from 'react' 2 | import {Dialog} from '../common/Dialog' 3 | import {RouterState} from '../router-state' 4 | import {useTranslation} from '../settings-state' 5 | import './NoWallet.scss' 6 | 7 | export const NoWallet: FunctionComponent<{}> = () => { 8 | const routerState = RouterState.useContainer() 9 | const {t} = useTranslation() 10 | 11 | return ( 12 |
13 | routerState.navigate('TXNS'), 20 | }} 21 | /> 22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/wallets/TermsAndConditions.scss: -------------------------------------------------------------------------------- 1 | @import '../sass-includes'; 2 | 3 | .TermsAndConditions { 4 | .scrollable { 5 | max-height: 30vh; 6 | margin-bottom: $dialog-component-spacing; 7 | padding-right: 1rem; 8 | overflow: auto; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/wallets/TermsAndConditionsStep.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Dialog} from '../common/Dialog' 3 | import {DialogApproval} from '../common/dialog/DialogApproval' 4 | import {useTranslation} from '../settings-state' 5 | import {TermsAndConditions} from './TermsAndConditions' 6 | import './TermsAndConditions.scss' 7 | 8 | interface TermsAndConditionsProps { 9 | next: () => void 10 | } 11 | 12 | export const TermsAndConditionsStep = ({next}: TermsAndConditionsProps): JSX.Element => { 13 | const {t} = useTranslation() 14 | 15 | return ( 16 | => next(), 21 | }} 22 | className={['TermsAndConditions']} 23 | > 24 |
25 | 26 |
27 | 32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/wallets/TransactionHistory.scss: -------------------------------------------------------------------------------- 1 | @import '../themify', '../partial', '../vars', '../functions'; 2 | 3 | #App .TransactionHistory { 4 | display: grid; 5 | grid-template-rows: auto 1fr; 6 | margin: 0; 7 | 8 | .svg { 9 | @include themify($themes) { 10 | stroke: themed('text-color'); 11 | fill: themed('text-color'); 12 | } 13 | } 14 | 15 | .toolbar { 16 | @include header-margin-top; 17 | 18 | display: grid; 19 | grid-template-columns: 1fr auto; 20 | line-height: 30px; 21 | } 22 | 23 | .no-transactions { 24 | @extend %empty-list-message; 25 | } 26 | 27 | .transactions-container { 28 | margin-top: 30px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/wallets/TransactionList.scss: -------------------------------------------------------------------------------- 1 | @import '../partial', '../vars'; 2 | 3 | .TransactionList { 4 | @extend %base-grid; 5 | 6 | .header { 7 | display: contents; 8 | position: sticky; 9 | top: 0; 10 | padding-left: 1em; 11 | font-size: $font-size-s; 12 | font-weight: bold; 13 | letter-spacing: $letter-spacing-large; 14 | line-height: 40px; 15 | text-align: left; 16 | 17 | .sortable { 18 | @extend %disable-focus-outline; 19 | 20 | cursor: pointer; 21 | user-select: none; 22 | 23 | .label { 24 | margin-right: 0.5em; 25 | } 26 | 27 | &:hover, 28 | &:focus { 29 | .label { 30 | text-decoration: underline; 31 | } 32 | } 33 | } 34 | } 35 | 36 | .body { 37 | display: contents; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/wallets/Wallet.scss: -------------------------------------------------------------------------------- 1 | .Wallet { 2 | margin-top: 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/wallets/Wallet.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {EmptyProps} from 'antd/lib/empty' 3 | import {TransactionHistory} from './TransactionHistory' 4 | import {withStatusGuard, PropsWithWalletState} from '../common/wallet-status-guard' 5 | import {LoadedState} from '../common/wallet-state' 6 | import './Wallet.scss' 7 | 8 | const _Wallet = ({ 9 | walletState: { 10 | transactions, 11 | availableBalance, 12 | accounts, 13 | estimateTransactionFee, 14 | getNextNonce, 15 | generateAccount, 16 | }, 17 | }: PropsWithWalletState): JSX.Element => { 18 | return ( 19 |
20 | 28 |
29 | ) 30 | } 31 | 32 | export const Wallet = withStatusGuard(_Wallet, 'LOADED') 33 | -------------------------------------------------------------------------------- /src/wallets/WalletActionBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SVG from 'react-inlinesvg' 3 | import {fillActionHandlers} from '../common/util' 4 | import './WalletActionBox.scss' 5 | 6 | interface WalletActionBoxProps { 7 | icon: string 8 | title: string 9 | description: string 10 | buttonLabel: string 11 | onClick: () => void 12 | } 13 | 14 | export const WalletActionBox = (props: WalletActionBoxProps): JSX.Element => { 15 | const {icon, title, description, buttonLabel, onClick} = props 16 | 17 | return ( 18 |
19 | 20 |
{title}
21 |
{description}
22 |
{buttonLabel}
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/wallets/WalletListSidebar.scss: -------------------------------------------------------------------------------- 1 | @import '../vars'; 2 | 3 | .WalletListSidebar { 4 | width: 234px; 5 | height: 100vh; 6 | overflow: auto; 7 | 8 | .wallet-links { 9 | margin: 0 0 16px 0; 10 | padding: 0; 11 | list-style-type: none; 12 | } 13 | 14 | .wallet-link { 15 | display: block; 16 | outline: 0; 17 | opacity: $secondary-opacity; 18 | font-size: $font-size-s; 19 | font-weight: bold; 20 | letter-spacing: $letter-spacing-small; 21 | line-height: 124px; 22 | text-align: center; 23 | text-decoration: none; 24 | cursor: pointer; 25 | 26 | &-active { 27 | opacity: 1; 28 | cursor: default; 29 | } 30 | 31 | &:after { 32 | content: ''; 33 | display: block; 34 | width: 66%; 35 | margin: 0 auto; 36 | border-bottom-width: 1px; 37 | border-bottom-style: solid; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/wallets/WalletListSidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent} from 'react' 2 | import classnames from 'classnames' 3 | import {fillActionHandlers} from '../common/util' 4 | import './WalletListSidebar.scss' 5 | 6 | interface Wallet { 7 | id: string 8 | name: string 9 | } 10 | 11 | interface WalletListSidebarProps { 12 | wallets: Wallet[] 13 | currentWalletId: string 14 | changeWallet(walletId: string): void 15 | } 16 | 17 | export const WalletListSidebar: FunctionComponent = ({ 18 | wallets, 19 | currentWalletId, 20 | changeWallet, 21 | }: WalletListSidebarProps) => { 22 | return ( 23 |
24 |
    25 | {wallets.map((wallet) => { 26 | const isActive = currentWalletId === wallet.id 27 | const classes = classnames('wallet-link', {'wallet-link-active': isActive}) 28 | return ( 29 |
  • 30 |
    changeWallet(wallet.id))}> 31 | {wallet.name} 32 |
    33 |
  • 34 | ) 35 | })} 36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/wallets/WalletPathChooser.scss: -------------------------------------------------------------------------------- 1 | .WalletPathChooser { 2 | display: grid; 3 | grid-template-columns: 1fr 1fr; 4 | justify-content: space-between; 5 | max-width: 1000px; 6 | margin: 0 auto; 7 | 8 | .column { 9 | display: flex; 10 | flex-direction: column; 11 | align-items: center; 12 | justify-content: center; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/wallets/WalletPathChooser.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {WalletActionBox} from './WalletActionBox' 3 | import {useTranslation} from '../settings-state' 4 | import walletIcon from '../assets/icons/wallet.svg' 5 | import walletRestoreIcon from '../assets/icons/wallet-restore.svg' 6 | import './WalletPathChooser.scss' 7 | 8 | interface WalletPathChooserProps { 9 | goToCreate: () => void 10 | goToRestore: () => void 11 | } 12 | 13 | export const WalletPathChooser = ({ 14 | goToCreate, 15 | goToRestore, 16 | }: WalletPathChooserProps): JSX.Element => { 17 | const {t} = useTranslation() 18 | 19 | return ( 20 |
21 |
22 | 29 |
30 |
31 | 38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/wallets/WalletSetup.scss: -------------------------------------------------------------------------------- 1 | @import '../sass-includes'; 2 | 3 | .WalletSetup { 4 | .content { 5 | margin: 3rem 0 0 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/wallets/Wallets.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, FunctionComponent} from 'react' 2 | import {text} from '@storybook/addon-knobs' 3 | import {ESSENTIAL_DECORATORS} from '../storybook-util/essential-decorators' 4 | import {WalletListSidebar} from './WalletListSidebar' 5 | import {NoWallet} from './NoWallet' 6 | 7 | export default { 8 | title: 'Wallets', 9 | decorators: ESSENTIAL_DECORATORS, 10 | } 11 | 12 | // `useState` cannot be used in stories 13 | // https://github.com/storybookjs/storybook/issues/4691 14 | const WalletList: FunctionComponent<{}> = () => { 15 | const [currentWalletId, changeWallet] = useState('1') 16 | 17 | return ( 18 | 36 | ) 37 | } 38 | 39 | export const walletList: FunctionComponent<{}> = () => 40 | 41 | export const noWallet = (): JSX.Element => 42 | -------------------------------------------------------------------------------- /src/wallets/Wallets.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react' 2 | import {pipe} from 'fp-ts/lib/function' 3 | import {option} from 'fp-ts' 4 | import {message} from 'antd' 5 | import {WalletState} from '../common/wallet-state' 6 | import {Navigate} from '../layout/Router' 7 | import {Loading} from '../common/Loading' 8 | import {Wallet} from './Wallet' 9 | import {useTranslation} from '../settings-state' 10 | 11 | export const Wallets = (): JSX.Element => { 12 | const walletState = WalletState.useContainer() 13 | 14 | const {translateError} = useTranslation() 15 | useEffect(() => { 16 | pipe( 17 | walletState.error, 18 | option.map(translateError), 19 | option.fold( 20 | () => void 0, 21 | (error) => { 22 | message.error({content: error, duration: 5, key: 'walletError'}) 23 | }, 24 | ), 25 | ) 26 | }, [walletState.error]) 27 | 28 | switch (walletState.walletStatus) { 29 | case 'INITIAL': { 30 | return 31 | } 32 | case 'LOADING': { 33 | return 34 | } 35 | case 'LOADED': { 36 | return 37 | } 38 | case 'NO_WALLET': { 39 | return 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/wallets/create/WalletCreateDefineStep.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, FunctionComponent} from 'react' 2 | import {Dialog} from '../../common/Dialog' 3 | import {DialogInput} from '../../common/dialog/DialogInput' 4 | import {DialogPassword} from '../../common/dialog/DialogPassword' 5 | import {useTranslation} from '../../settings-state' 6 | 7 | interface WalletCreateDefineStepProps { 8 | cancel: () => void 9 | next: (walletName: string, password: string) => Promise 10 | errors?: React.ReactNode 11 | } 12 | 13 | export const WalletCreateDefineStep: FunctionComponent = ({ 14 | cancel, 15 | next, 16 | errors, 17 | }: WalletCreateDefineStepProps) => { 18 | const {t} = useTranslation() 19 | const [walletName, setWalletName] = useState('') 20 | const [password, setPassword] = useState('') 21 | 22 | return ( 23 | => next(walletName, password), 28 | }} 29 | footer={errors} 30 | > 31 | setWalletName(e.target.value)} 36 | formItem={{ 37 | name: 'wallet-name', 38 | rules: [{required: true, message: t(['wallet', 'error', 'walletNameShouldNotBeEmpty'])}], 39 | }} 40 | /> 41 | 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/wallets/history/BatchRange.test.ts: -------------------------------------------------------------------------------- 1 | import {assert} from 'chai' 2 | import * as fc from 'fast-check' 3 | import {BatchRange} from './BatchRange' 4 | 5 | describe('BatchRange', () => { 6 | it('should clamp min to be greater or equal than 0 when creating with min and count', () => { 7 | fc.assert( 8 | fc.property(fc.integer(), fc.nat(), (min, size) => { 9 | const result = BatchRange.ofSize(min, size) 10 | 11 | assert(result.min >= 0) 12 | }), 13 | ) 14 | }) 15 | 16 | it('should clamp min to be greater or equal than 0 when creating with max and count', () => { 17 | fc.assert( 18 | fc.property(fc.integer(), fc.nat(), (max, size) => { 19 | const result = BatchRange.ofSizeFromMax(max, size) 20 | 21 | assert(result.min >= 0) 22 | }), 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/wallets/history/BatchRange.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash/fp' 2 | 3 | export interface BatchRange { 4 | min: number 5 | max: number 6 | } 7 | 8 | const negativeToZero = (nr: number): number => (Number.isInteger(nr) && nr >= 0 ? nr : 0) 9 | 10 | export const BatchRange = { 11 | ofSize: (min: number, size: number): BatchRange => ({ 12 | min: negativeToZero(min), 13 | max: min + size - 1, 14 | }), 15 | ofSizeFromMax: (max: number, size: number): BatchRange => ({ 16 | min: negativeToZero(max - size + 1), 17 | max, 18 | }), 19 | lower: (a: BatchRange, b: BatchRange): BatchRange => (a.min <= b.min ? a : b), 20 | isEqual: (a: BatchRange, b: BatchRange): boolean => _.isEqual(a, b), 21 | contains: (nr: number, range: BatchRange): boolean => range.min <= nr && nr <= range.max, 22 | follows: (nr: number, range: BatchRange): boolean => nr + 1 == range.min, 23 | } 24 | -------------------------------------------------------------------------------- /src/wallets/history/StoredHistory.ts: -------------------------------------------------------------------------------- 1 | import {TransactionHistory} from './TransactionHistory' 2 | 3 | export interface StoredHistory { 4 | lastCheckedBlock: number 5 | blocksWithKnownTransactions: readonly number[] 6 | } 7 | export const empty: StoredHistory = {lastCheckedBlock: 0, blocksWithKnownTransactions: []} 8 | export const fromHistory = (txHistory: TransactionHistory): StoredHistory => { 9 | return { 10 | lastCheckedBlock: txHistory.lastCheckedBlock, 11 | blocksWithKnownTransactions: [ 12 | ...new Set( 13 | txHistory.transactions 14 | .map((tx) => tx.blockNumber) 15 | .filter((nr): nr is number => Number.isInteger(nr)), 16 | ), 17 | ], 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/wallets/history/index.ts: -------------------------------------------------------------------------------- 1 | export * from './HistoryStore' 2 | export * from './TransactionHistory' 3 | export * from './TransactionHistoryService' 4 | export * from './Transaction' 5 | -------------------------------------------------------------------------------- /src/wallets/modals/ExportPrivateKey.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .ExportPrivateKey { 5 | font-size: $font-size-s; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/wallets/modals/ReceiveTransaction.scss: -------------------------------------------------------------------------------- 1 | @import '../../sass-includes'; 2 | 3 | @include dialog-scoped { 4 | .ReceiveModal { 5 | .footer { 6 | .title { 7 | padding: 0 0 0.5rem; 8 | opacity: $secondary-opacity; 9 | font-size: $font-size-s; 10 | font-weight: bold; 11 | text-transform: uppercase; 12 | } 13 | 14 | .address { 15 | width: 508px; 16 | padding: 0.5rem 0; 17 | overflow: hidden; 18 | opacity: $secondary-opacity; 19 | font-size: $font-size-s; 20 | white-space: nowrap; 21 | 22 | &:hover { 23 | opacity: 1; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/wallets/sendTransaction/common.ts: -------------------------------------------------------------------------------- 1 | export enum TransactionType { 2 | basic = 'BASIC', 3 | advanced = 'ADVANCED', 4 | } 5 | 6 | export interface BasicTransactionParams { 7 | amount: string 8 | fee: string 9 | recipient: string 10 | } 11 | 12 | export interface AdvancedTransactionParams { 13 | amount: string 14 | gasLimit: string 15 | gasPrice: string 16 | recipient: string 17 | data: string 18 | nonce: string 19 | } 20 | -------------------------------------------------------------------------------- /src/wallets/sendTransaction/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common' 2 | export * from './SendBasicTransaction' 3 | export * from './SendAdvancedTransaction' 4 | export * from './ConfirmBasicTransaction' 5 | export * from './ConfirmAdvancedTransaction' 6 | -------------------------------------------------------------------------------- /tsconfig.bin.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "module": "commonjs" 6 | }, 7 | "include": [ 8 | "bin/**/*.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "jsx": "react-jsx", 10 | "allowJs": false, 11 | "alwaysStrict": true, 12 | "skipLibCheck": true, 13 | "sourceMap": true, 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noEmit": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": [ 26 | "src" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.main.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "module": "CommonJS", 6 | "outDir": "./build" 7 | }, 8 | "include": [ 9 | "src/main/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /useful-links.md: -------------------------------------------------------------------------------- 1 | # Useful Links 2 | 3 | ## General 4 | 5 | - [Development methodology](https://input-output.atlassian.net/wiki/spaces/MID/pages/393412651/Development+Methodology) 6 | - [Mantis Wallet](https://docs.google.com/document/d/1Op68feCZmoEyhHVgHeuEfzoiTFUMFxAK9EjGb7i-7T0/) 7 | 8 | ## Learning Resources / Reference 9 | 10 | - [Ant Design Components](https://ant.design/components/overview/) 11 | - [react-testing-library docs](https://testing-library.com/docs/react-testing-library/intro) 12 | - [Storybook.js docs](https://storybook.js.org/docs/react/get-started/introduction) 13 | - [unstated-next docs](https://github.com/jamiebuilds/unstated-next/blob/master/README.md) 14 | - [fp-ts docs](https://gcanti.github.io/fp-ts/) 15 | - [io-ts docs](https://gcanti.github.io/io-ts/) 16 | - [Insomnia](https://insomnia.rest/) 17 | Install Insomnia and load `insomnia_workspace.json` to play with the backend. 18 | - [ERC20 Tokens explained](https://cointelegraph.com/explained/erc-20-tokens-explained) 19 | - [web3.js docs](https://web3js.readthedocs.io/en/v1.3.0/) 20 | -------------------------------------------------------------------------------- /webpack.main.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CopyPkgJsonPlugin = require('copy-pkg-json-webpack-plugin') 3 | 4 | const mainConfig = { 5 | devtool: false, 6 | mode: 'production', 7 | entry: './src/main/main.ts', 8 | target: 'electron-main', 9 | output: {path: path.join(__dirname, 'build/main'), filename: 'main.js'}, 10 | node: {__dirname: false, __filename: false}, 11 | resolve: { 12 | extensions: ['.js', '.json', '.ts', '.tsx'], 13 | }, 14 | plugins: [ 15 | new CopyPkgJsonPlugin({ 16 | remove: ['scripts', 'husky', 'jest', 'devDependencies', 'browserslist'], 17 | }), 18 | ], 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(ts|tsx)$/, 23 | exclude: /node_modules/, 24 | loader: 'ts-loader', 25 | options: { 26 | configFile: path.join(__dirname, 'tsconfig.main.json'), 27 | }, 28 | }, 29 | ], 30 | }, 31 | } 32 | 33 | module.exports = [mainConfig] 34 | --------------------------------------------------------------------------------