├── .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 |
4 |
--------------------------------------------------------------------------------
/public/icons/chains/ethereum.svg:
--------------------------------------------------------------------------------
1 |
2 |
25 |
--------------------------------------------------------------------------------
/public/icons/check-double.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/icons/check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/icons/circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/icons/clock.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/icons/copy.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/public/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/public/icons/edit.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/public/icons/exchange.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/icons/hourglass.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/public/icons/refresh.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/public/icons/sum.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/icons/wallet.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/chains/ethereum.svg:
--------------------------------------------------------------------------------
1 |
2 |
25 |
--------------------------------------------------------------------------------
/src/assets/icons/check-double.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/clock.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/copy.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/assets/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/icons/edit.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/icons/exchange.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/hourglass.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/assets/icons/refresh.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/icons/sum.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/wallet.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
26 |
--------------------------------------------------------------------------------
/src/assets/logoCrop.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------