├── .nvmrc
├── public
├── _redirects
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── mstile-150x150.png
├── fonts
│ ├── MarkPro-Bold.eot
│ ├── MarkPro-Book.eot
│ ├── SFMono-Bold.eot
│ ├── SFMono-Bold.ttf
│ ├── SFMono-Bold.woff
│ ├── MarkPro-Bold.woff
│ ├── MarkPro-Bold.woff2
│ ├── MarkPro-Book.woff
│ ├── MarkPro-Book.woff2
│ ├── MarkPro-Medium.eot
│ ├── MarkPro-Medium.woff
│ ├── SFMono-Bold.woff2
│ ├── SFMono-Medium.eot
│ ├── SFMono-Medium.ttf
│ ├── SFMono-Medium.woff
│ ├── SFMono-Medium.woff2
│ ├── SFMono-Regular.eot
│ ├── SFMono-Regular.ttf
│ ├── SFMono-Regular.woff
│ ├── SFMono-Semibold.eot
│ ├── SFMono-Semibold.ttf
│ ├── SFProText-Bold.eot
│ ├── SFProText-Bold.ttf
│ ├── SFProText-Bold.woff
│ ├── MarkPro-Medium.woff2
│ ├── SFMono-Regular.woff2
│ ├── SFMono-Semibold.woff
│ ├── SFMono-Semibold.woff2
│ ├── SFProText-Bold.woff2
│ ├── SFProText-Medium.eot
│ ├── SFProText-Medium.ttf
│ ├── SFProText-Medium.woff
│ ├── SFProText-Regular.eot
│ ├── SFProText-Regular.ttf
│ ├── SFProText-Medium.woff2
│ ├── SFProText-Regular.woff
│ ├── SFProText-Regular.woff2
│ ├── SFProText-Semibold.eot
│ ├── SFProText-Semibold.ttf
│ ├── SFProText-Semibold.woff
│ └── SFProText-Semibold.woff2
├── social-media-card.png
├── android-chrome-144x144.png
├── unregisterServiceWorker.js
├── manifest.json
├── site.webmanifest
├── blockies.min.js
├── index.html
└── safari-pinned-tab.svg
├── src
├── dump.rdb
├── assets
│ ├── spinner.png
│ ├── clipboard.png
│ ├── ledger-logo.png
│ ├── logo-light.png
│ ├── placeholder.png
│ ├── qr-code-bnw.png
│ ├── trezor-logo.png
│ ├── metamask-logo.png
│ ├── metamask-white.png
│ ├── tab-background.png
│ ├── triangles-blue.png
│ ├── triangles-dark.png
│ ├── metamask-original.png
│ ├── tab-background-sprite.png
│ ├── eth.svg
│ ├── arrow-received.svg
│ ├── arrow-sent.svg
│ ├── ethplorer-logo.svg
│ ├── selector-grey.svg
│ ├── arrow-up.svg
│ ├── arrow-left.svg
│ ├── walletconnect-white.svg
│ ├── transactions-tab.svg
│ ├── star-tab.svg
│ ├── arrow-failed.svg
│ ├── help.svg
│ ├── etherscan-logo.svg
│ ├── cross.svg
│ ├── circle.svg
│ ├── exchange-icon.svg
│ ├── qr-code-transparent.svg
│ ├── caret.svg
│ ├── walletconnect-blue.svg
│ ├── opera-text.svg
│ ├── triangles-light.svg
│ ├── balances-tab.svg
│ ├── caret-small.svg
│ ├── opera-logo.svg
│ ├── gbp.svg
│ ├── trustwallet-logo.svg
│ ├── btc.svg
│ ├── eur.svg
│ ├── firefox-text.svg
│ ├── convert-icon.svg
│ ├── usd.svg
│ ├── chrome-text.svg
│ ├── mail.svg
│ ├── brave-text.svg
│ ├── interactions-tab.svg
│ ├── chrome-logo.svg
│ ├── globe.svg
│ ├── brave-logo.svg
│ └── trezor-logo.svg
├── history.js
├── piwik.js
├── references
│ ├── smartcontract-methods.json
│ ├── time-units.json
│ ├── ethereum-networks.json
│ ├── supported-wallets.json
│ ├── native-currencies.json
│ └── ethereum-units.json
├── modals
│ ├── ApproveTransactionModal
│ │ ├── styles.js
│ │ └── index.js
│ ├── SuccessModal
│ │ └── index.js
│ ├── ReceiveModal
│ │ └── index.js
│ ├── WalletConnectModal.js
│ └── index.js
├── helpers
│ ├── device.js
│ ├── bootIntercom.js
│ └── intercom.js
├── components
│ ├── GasPriceLineBreak
│ │ └── index.js
│ ├── Link.js
│ ├── GasButton
│ │ ├── styles.js
│ │ └── index.js
│ ├── GasPanel
│ │ ├── styles.js
│ │ └── index.js
│ ├── LedgerLogo.js
│ ├── TrezorLogo.js
│ ├── MetamaskLogo.js
│ ├── WalletConnectLogo.js
│ ├── Spinner.js
│ ├── Background.js
│ ├── AccountType.js
│ ├── HoverWrapper.js
│ ├── Wrapper.js
│ ├── AssetIcon.js
│ ├── Form.js
│ ├── Column.js
│ ├── TextButton.js
│ ├── QRCodeDisplay.js
│ ├── Blockie.js
│ ├── ClickOutside.js
│ ├── LineBreak.js
│ ├── ToggleIndicator.js
│ ├── Notification.js
│ ├── Loader.js
│ ├── QRCodeReader.js
│ ├── Footer.js
│ ├── Warning.js
│ ├── CopyToClipboard
│ │ ├── index.js
│ │ └── styles.js
│ ├── ReminderRibbon.js
│ ├── TransactionStatus.js
│ ├── ButtonCustom.js
│ ├── Card.js
│ ├── Input.js
│ └── Button.js
├── handlers
│ ├── localstorage.js
│ ├── walletconnect.js
│ ├── trezor-eth.js
│ ├── web3.js
│ └── ledger-eth.js
├── reducers
│ ├── index.js
│ ├── _modal.js
│ ├── _notification.js
│ ├── _zrxinstant.js
│ ├── _ledger.js
│ ├── _warning.js
│ ├── _trezor.js
│ └── _metamask.js
├── pages
│ ├── NotFound.js
│ ├── Wallet.js
│ ├── Metamask.js
│ ├── Ledger.js
│ └── Trezor.js
├── Root.js
├── index.js
├── Router.js
└── views
│ └── Account
│ └── AccountUniqueTokens.js
├── .prettierrc
├── .gitmodules
├── .eslintrc
├── .editorconfig
├── .gitignore
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE.md
├── README.md
└── package.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 11.2.0
2 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/src/dump.rdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/dump.rdb
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "parser": "flow"
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/spinner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/spinner.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/src/assets/clipboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/clipboard.png
--------------------------------------------------------------------------------
/src/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from 'history';
2 | export default createBrowserHistory();
3 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/mstile-150x150.png
--------------------------------------------------------------------------------
/src/assets/ledger-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/ledger-logo.png
--------------------------------------------------------------------------------
/src/assets/logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/logo-light.png
--------------------------------------------------------------------------------
/src/assets/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/placeholder.png
--------------------------------------------------------------------------------
/src/assets/qr-code-bnw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/qr-code-bnw.png
--------------------------------------------------------------------------------
/src/assets/trezor-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/trezor-logo.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "public/tokens"]
2 | path = public/tokens
3 | url = https://github.com/balance-io/tokens.git
4 |
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Bold.eot
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Book.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Book.eot
--------------------------------------------------------------------------------
/public/fonts/SFMono-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Bold.eot
--------------------------------------------------------------------------------
/public/fonts/SFMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Bold.ttf
--------------------------------------------------------------------------------
/public/fonts/SFMono-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Bold.woff
--------------------------------------------------------------------------------
/public/social-media-card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/social-media-card.png
--------------------------------------------------------------------------------
/src/assets/metamask-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/metamask-logo.png
--------------------------------------------------------------------------------
/src/assets/metamask-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/metamask-white.png
--------------------------------------------------------------------------------
/src/assets/tab-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/tab-background.png
--------------------------------------------------------------------------------
/src/assets/triangles-blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/triangles-blue.png
--------------------------------------------------------------------------------
/src/assets/triangles-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/triangles-dark.png
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Bold.woff
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Bold.woff2
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Book.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Book.woff
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Book.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Book.woff2
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Medium.eot
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Medium.woff
--------------------------------------------------------------------------------
/public/fonts/SFMono-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Bold.woff2
--------------------------------------------------------------------------------
/public/fonts/SFMono-Medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Medium.eot
--------------------------------------------------------------------------------
/public/fonts/SFMono-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Medium.ttf
--------------------------------------------------------------------------------
/public/fonts/SFMono-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Medium.woff
--------------------------------------------------------------------------------
/public/fonts/SFMono-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Medium.woff2
--------------------------------------------------------------------------------
/public/fonts/SFMono-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Regular.eot
--------------------------------------------------------------------------------
/public/fonts/SFMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Regular.ttf
--------------------------------------------------------------------------------
/public/fonts/SFMono-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Regular.woff
--------------------------------------------------------------------------------
/public/fonts/SFMono-Semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Semibold.eot
--------------------------------------------------------------------------------
/public/fonts/SFMono-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Semibold.ttf
--------------------------------------------------------------------------------
/public/fonts/SFProText-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Bold.eot
--------------------------------------------------------------------------------
/public/fonts/SFProText-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Bold.ttf
--------------------------------------------------------------------------------
/public/fonts/SFProText-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Bold.woff
--------------------------------------------------------------------------------
/src/assets/metamask-original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/metamask-original.png
--------------------------------------------------------------------------------
/public/android-chrome-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/android-chrome-144x144.png
--------------------------------------------------------------------------------
/public/fonts/MarkPro-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/MarkPro-Medium.woff2
--------------------------------------------------------------------------------
/public/fonts/SFMono-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Regular.woff2
--------------------------------------------------------------------------------
/public/fonts/SFMono-Semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Semibold.woff
--------------------------------------------------------------------------------
/public/fonts/SFMono-Semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFMono-Semibold.woff2
--------------------------------------------------------------------------------
/public/fonts/SFProText-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Bold.woff2
--------------------------------------------------------------------------------
/public/fonts/SFProText-Medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Medium.eot
--------------------------------------------------------------------------------
/public/fonts/SFProText-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Medium.ttf
--------------------------------------------------------------------------------
/public/fonts/SFProText-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Medium.woff
--------------------------------------------------------------------------------
/public/fonts/SFProText-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Regular.eot
--------------------------------------------------------------------------------
/public/fonts/SFProText-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Regular.ttf
--------------------------------------------------------------------------------
/public/fonts/SFProText-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Medium.woff2
--------------------------------------------------------------------------------
/public/fonts/SFProText-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Regular.woff
--------------------------------------------------------------------------------
/public/fonts/SFProText-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Regular.woff2
--------------------------------------------------------------------------------
/public/fonts/SFProText-Semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Semibold.eot
--------------------------------------------------------------------------------
/public/fonts/SFProText-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Semibold.ttf
--------------------------------------------------------------------------------
/public/fonts/SFProText-Semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Semibold.woff
--------------------------------------------------------------------------------
/public/fonts/SFProText-Semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/public/fonts/SFProText-Semibold.woff2
--------------------------------------------------------------------------------
/src/assets/tab-background-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-me/rainbow-dashboard/HEAD/src/assets/tab-background-sprite.png
--------------------------------------------------------------------------------
/src/piwik.js:
--------------------------------------------------------------------------------
1 | import PiwikReactRouter from 'piwik-react-router';
2 |
3 | const piwik = PiwikReactRouter({
4 | url: 'https://matomo.balance.io',
5 | siteId: 1,
6 | });
7 |
8 | export default piwik;
9 |
--------------------------------------------------------------------------------
/public/unregisterServiceWorker.js:
--------------------------------------------------------------------------------
1 | navigator.serviceWorker.getRegistrations().then(function(registrations) {
2 | for (let registration of registrations) {
3 | registration.unregister();
4 | }
5 | });
6 |
--------------------------------------------------------------------------------
/src/assets/eth.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/references/smartcontract-methods.json:
--------------------------------------------------------------------------------
1 | {
2 | "token_transfer": {
3 | "method": "transfer(address,uint256)",
4 | "hash": "0xa9059cbb"
5 | },
6 | "token_balance": {
7 | "method": "balanceOf(address)",
8 | "hash": "0x70a08231"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/assets/arrow-received.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["prettier"],
3 | "extends": ["react-app", "prettier"],
4 | "rules": {
5 | "jsx-a11y/href-no-hash": "off",
6 | "array-callback-return": "off",
7 | "prettier/prettier": "error"
8 | },
9 | "globals": {
10 | "document": true,
11 | "window": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/references/time-units.json:
--------------------------------------------------------------------------------
1 | {
2 | "ms": {
3 | "second": 1000,
4 | "minute": 60000,
5 | "hour": 3600000,
6 | "day": 86400000
7 | },
8 | "secs": {
9 | "minute": 60,
10 | "hour": 3600,
11 | "day": 86400
12 | },
13 | "mins": {
14 | "hour": 60,
15 | "day": 1440
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/assets/arrow-sent.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/references/ethereum-networks.json:
--------------------------------------------------------------------------------
1 | {
2 | "mainnet": {
3 | "id": 1,
4 | "value": "Mainnet"
5 | },
6 | "ropsten": {
7 | "id": 3,
8 | "value": "Ropsten"
9 | },
10 | "rinkeby": {
11 | "id": 4,
12 | "value": "Rinkeby"
13 | },
14 | "kovan": {
15 | "id": 42,
16 | "value": "Kovan"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/modals/ApproveTransactionModal/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const StyledApproveTransaction = styled.div`
4 | padding: 22px;
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | & > div {
9 | margin-top: 15px;
10 | }
11 | & > p {
12 | margin-top: 32px;
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/src/assets/ethplorer-logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/references/supported-wallets.json:
--------------------------------------------------------------------------------
1 | {
2 | "balance_wallet": {
3 | "color": "white",
4 | "value": "Balance Wallet"
5 | },
6 | "metamask": {
7 | "color": "orange",
8 | "value": "MetaMask"
9 | },
10 | "ledger": {
11 | "color": "blue",
12 | "value": "Ledger"
13 | },
14 | "trezor": {
15 | "color": "black",
16 | "value": "Trezor"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Balance Manager",
3 | "name": "Balance Manager",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # Matches multiple files with brace expansion notation
14 | # Set default charset
15 | [*.{js,py}]
16 | charset = utf-8
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | IGNORE_*
24 | package-lock.json
25 |
--------------------------------------------------------------------------------
/src/assets/selector-grey.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### PR Checklist (check all)
2 |
3 | * [ ] Updated `CHANGELOG.md` to describe the included fixes and changes made
4 | * [ ] Included issue number on the description of the PR
5 | * [ ] Commented on the relevant issue thread about this PR
6 |
7 | ### Issue number
8 |
9 |
10 |
11 | ### Changelog
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/helpers/device.js:
--------------------------------------------------------------------------------
1 | import bowser from 'bowser';
2 |
3 | const isMobile = () => {
4 | const browser = bowser.getParser(window.navigator.userAgent);
5 |
6 | return browser.some(['mobile', 'tablet']);
7 | };
8 |
9 | const isValidBrowser = () => {
10 | const browser = bowser.getParser(window.navigator.userAgent);
11 |
12 | return !browser.some(['Internet Explorer', 'Microsoft Edge', 'Safari']);
13 | };
14 |
15 | export { isMobile, isValidBrowser };
16 |
--------------------------------------------------------------------------------
/src/assets/arrow-up.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/walletconnect-white.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/transactions-tab.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### What is this issue about? (select one)
2 |
3 | * [ ] Bug report
4 | * [ ] Feature request
5 | * [ ] Enhancement
6 |
7 | ### Issue Description
8 |
9 |
10 |
11 | ## Steps to reproduce
12 |
13 | 1.
14 | 2.
15 | 3.
16 |
17 | ### Expected Behavior
18 |
19 |
20 |
21 | ### Actual Behavior
22 |
23 |
24 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/GasPriceLineBreak/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LineBreak from '../../components/LineBreak';
3 |
4 | const GasPriceLineBreak = ({ gasPriceOption }) => (
5 |
18 | );
19 |
20 | export default GasPriceLineBreak;
21 |
--------------------------------------------------------------------------------
/src/assets/star-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/src/helpers/bootIntercom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @desc boot intercom
3 | * @return {Intercom}
4 | */
5 | export const bootIntercom = () => {
6 | let appID;
7 | switch (process.env.NODE_ENV) {
8 | case 'development':
9 | break;
10 | case 'test':
11 | appID = 'k8c9ptl1';
12 | break;
13 | case 'production':
14 | appID = 'j0fl7v0m';
15 | break;
16 | default:
17 | return;
18 | }
19 | const setup = () => window.Intercom('boot', { app_id: appID });
20 | if (typeof window.Intercom !== 'undefined') setup();
21 | else setTimeout(setup, 500);
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/Link.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { Link } from 'react-router-dom';
5 |
6 | const SLink = styled(Link)`
7 | text-decoration: none;
8 | color: inherit;
9 | margin: 0;
10 | padding: 0;
11 | width: ${({ width }) => (width ? width : null)};
12 | `;
13 |
14 | const LinkWrapper = ({ children, ...props }) => (
15 | {children}
16 | );
17 |
18 | LinkWrapper.propTypes = {
19 | children: PropTypes.node.isRequired,
20 | };
21 |
22 | export default LinkWrapper;
23 |
--------------------------------------------------------------------------------
/src/components/GasButton/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import Button from '../../components/Button';
4 |
5 | export const StyledGasButton = styled(Button)`
6 | min-height: 54px;
7 | width: 100%;
8 |
9 | & p {
10 | line-height: 20px;
11 | margin-top: 2px;
12 | }
13 |
14 | & p:first-child {
15 | font-weight: 500;
16 | }
17 |
18 | & p:last-child {
19 | font-weight: 400;
20 | font-size: 12px;
21 | }
22 |
23 | &:hover,
24 | &:active,
25 | &:focus,
26 | &:disabled {
27 | outline: none !important;
28 | box-shadow: none !important;
29 | }
30 | `;
31 |
--------------------------------------------------------------------------------
/src/components/GasButton/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { lang } from 'balance-common';
4 |
5 | import { StyledGasButton } from './styles';
6 |
7 | const GasButton = ({ gasPrice, speed, onClick }) => (
8 | onClick(speed)}>
9 | {`${lang.t(`modal.gas_${speed}`)}: ${
10 | gasPrice && gasPrice.txFee.native
11 | ? gasPrice.txFee.native.value.display
12 | : '$0.00'
13 | }`}
14 | {`~ ${gasPrice ? gasPrice.estimatedTime.display : '0 secs'}`}
15 |
16 | );
17 |
18 | export default GasButton;
19 |
--------------------------------------------------------------------------------
/src/handlers/localstorage.js:
--------------------------------------------------------------------------------
1 | import { commonStorage } from 'balance-common';
2 |
3 | /**
4 | * @desc get suppress reminder ribbon setting
5 | * @return {Boolean}
6 | */
7 | export const getSupressReminderRibbon = async () => {
8 | const reminderRibbon = await commonStorage.getLocal('supressreminderribbon');
9 | return reminderRibbon ? reminderRibbon.data : null;
10 | };
11 |
12 | /**
13 | * @desc save suppress reminder ribbon setting
14 | * @param {Boolean} [supress state]
15 | */
16 | export const saveSupressReminderRibbon = async state => {
17 | await commonStorage.saveLocal('supressreminderribbon', { data: state });
18 | };
19 |
--------------------------------------------------------------------------------
/src/assets/arrow-failed.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/help.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/etherscan-logo.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import exchange from './_exchange';
3 | import modal from './_modal';
4 | import ledger from './_ledger';
5 | import trezor from './_trezor';
6 | import metamask from './_metamask';
7 | import walletconnect from './_walletconnect';
8 | import notification from './_notification';
9 | import warning from './_warning';
10 | import zrxinstant from './_zrxinstant';
11 | import { account, send } from 'balance-common';
12 |
13 | export default combineReducers({
14 | exchange,
15 | send,
16 | account,
17 | modal,
18 | ledger,
19 | trezor,
20 | metamask,
21 | notification,
22 | warning,
23 | walletconnect,
24 | zrxinstant,
25 | });
26 |
--------------------------------------------------------------------------------
/src/components/GasPanel/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { colors } from '../../styles';
4 |
5 | export const StyledGasOptions = styled.div`
6 | display: flex;
7 | justify-content: space-between;
8 | padding: 0;
9 |
10 | & button {
11 | background-color: transparent;
12 | box-shadow: none;
13 | color: rgb(${colors.darkGrey});
14 | min-height: 64px;
15 | margin: 0;
16 | border-radius: 0;
17 |
18 | &:hover,
19 | &:active,
20 | &:focus {
21 | box-shadow: none !important;
22 | outline: none !important;
23 | background-color: transparent !important;
24 | color: rgb(${colors.darkGrey}) !important;
25 | }
26 | }
27 | `;
28 |
--------------------------------------------------------------------------------
/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { lang } from 'balance-common';
4 | import BaseLayout from '../layouts/base';
5 | import Link from '../components/Link';
6 |
7 | const StyledWrapper = styled.div`
8 | width: 100%;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | padding: 20px;
13 | text-transform: uppercase;
14 | text-align: center;
15 | height: 360px;
16 | `;
17 |
18 | const NotFound = () => (
19 |
20 |
21 |
22 | {lang.t('message.page_not_found')}
23 |
24 |
25 |
26 | );
27 | export default NotFound;
28 |
--------------------------------------------------------------------------------
/src/components/LedgerLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import ledgerLogo from '../assets/ledger-logo.png';
5 |
6 | const StyledLedgerLogo = styled.div`
7 | width: ${({ size }) => `${size}px`};
8 | height: ${({ size }) => `${size * 0.25}px`};
9 | & img {
10 | width: 100%;
11 | }
12 | `;
13 |
14 | const LedgerLogo = ({ size, ...props }) => (
15 |
16 |
17 |
18 | );
19 |
20 | LedgerLogo.propTypes = {
21 | size: PropTypes.number,
22 | };
23 |
24 | LedgerLogo.defaultProps = {
25 | size: 300,
26 | };
27 |
28 | export default LedgerLogo;
29 |
--------------------------------------------------------------------------------
/src/components/TrezorLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import trezorLogo from '../assets/trezor-logo.png';
5 |
6 | const StyledTrezorLogo = styled.div`
7 | width: ${({ size }) => `${size}px`};
8 | height: ${({ size }) => `${size * 0.3}px`};
9 | & img {
10 | width: 100%;
11 | }
12 | `;
13 |
14 | const TrezorLogo = ({ size, ...props }) => (
15 |
16 |
17 |
18 | );
19 |
20 | TrezorLogo.propTypes = {
21 | size: PropTypes.number,
22 | };
23 |
24 | TrezorLogo.defaultProps = {
25 | size: 275,
26 | };
27 |
28 | export default TrezorLogo;
29 |
--------------------------------------------------------------------------------
/src/assets/cross.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/components/MetamaskLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import metamaskOriginal from '../assets/metamask-original.png';
5 |
6 | const StyledMetamaskLogo = styled.div`
7 | width: ${({ size }) => `${size}px`};
8 | height: ${({ size }) => `${size * 0.925}px`};
9 | & img {
10 | width: 100%;
11 | }
12 | `;
13 |
14 | const MetamaskLogo = ({ size, ...props }) => (
15 |
16 |
17 |
18 | );
19 |
20 | MetamaskLogo.propTypes = {
21 | size: PropTypes.number,
22 | };
23 |
24 | MetamaskLogo.defaultProps = {
25 | size: 200,
26 | };
27 |
28 | export default MetamaskLogo;
29 |
--------------------------------------------------------------------------------
/src/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createStore, applyMiddleware } from 'redux';
3 | import { Provider } from 'react-redux';
4 | import { Router } from 'react-router-dom';
5 | import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
6 | import ReduxThunk from 'redux-thunk';
7 | import history from './history';
8 | import piwik from './piwik';
9 | import reducers from './reducers';
10 | import WalletRouter from './Router';
11 |
12 | const store = createStore(
13 | reducers,
14 | composeWithDevTools(applyMiddleware(ReduxThunk)),
15 | );
16 |
17 | const Root = () => (
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | export default Root;
26 |
--------------------------------------------------------------------------------
/src/assets/circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/references/native-currencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "USD": {
3 | "symbol": "$",
4 | "currency": "USD",
5 | "decimals": 2,
6 | "alignment": "left",
7 | "assetLimit": 1
8 | },
9 | "GBP": {
10 | "symbol": "£",
11 | "currency": "GBP",
12 | "decimals": 2,
13 | "alignment": "left",
14 | "assetLimit": 1
15 | },
16 | "EUR": {
17 | "symbol": "€",
18 | "currency": "EUR",
19 | "decimals": 2,
20 | "alignment": "left",
21 | "assetLimit": 1
22 | },
23 | "BTC": {
24 | "symbol": "₿",
25 | "currency": "BTC",
26 | "decimals": 8,
27 | "alignment": "right",
28 | "assetLimit": 0.0001
29 | },
30 | "ETH": {
31 | "symbol": "Ξ",
32 | "currency": "ETH",
33 | "decimals": 8,
34 | "alignment": "right",
35 | "assetLimit": 0.001
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/WalletConnectLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import walletConnectBlue from '../assets/walletconnect-blue.svg';
5 |
6 | const StyledWalletConnectLogo = styled.div`
7 | width: ${({ size }) => `${size}px`};
8 | height: ${({ size }) => `${size * 0.68}px`};
9 | & img {
10 | width: 100%;
11 | }
12 | `;
13 |
14 | const WalletConnectLogo = ({ size, ...props }) => (
15 |
16 |
17 |
18 | );
19 |
20 | WalletConnectLogo.propTypes = {
21 | size: PropTypes.number,
22 | };
23 |
24 | WalletConnectLogo.defaultProps = {
25 | size: 250,
26 | };
27 |
28 | export default WalletConnectLogo;
29 |
--------------------------------------------------------------------------------
/src/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { keyframes } from 'styled-components';
4 | import spinnerImg from '../assets/spinner.png';
5 |
6 | const spin = keyframes`
7 | 0% {
8 | transform: rotate(0deg);
9 | }
10 | 100% {
11 | transform: rotate(360deg);
12 | }
13 | `;
14 |
15 | const StyledSpinner = styled.img`
16 | width: ${({ size }) => `${size}px`};
17 | height: ${({ size }) => `${size}px`};
18 |
19 | animation: ${spin} 0.8s linear infinite;
20 | `;
21 |
22 | const Spinner = ({ size, ...props }) => (
23 |
24 | );
25 |
26 | Spinner.propTypes = {
27 | size: PropTypes.number,
28 | };
29 |
30 | Spinner.defaultProps = {
31 | size: 8,
32 | };
33 |
34 | export default Spinner;
35 |
--------------------------------------------------------------------------------
/src/components/Background.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const StyledBackgroundFixed = styled.div`
5 | position: fixed;
6 | top: 0;
7 | left: 0;
8 | width: 100vw;
9 | height: 100vh;
10 | z-index: -99999;
11 | `;
12 |
13 | const StyledBackgroundRelative = styled.div`
14 | position: relative;
15 | width: 100%;
16 | height: 100%;
17 | `;
18 |
19 | const StyledBackgroundBlue = styled.div`
20 | position: fixed;
21 | bottom: 0;
22 | left: 0;
23 | width: 150px;
24 | height: 225px;
25 | & img {
26 | width: 100%;
27 | }
28 | `;
29 |
30 | const Background = () => (
31 |
32 |
33 |
34 |
35 |
36 | );
37 |
38 | export default Background;
39 |
--------------------------------------------------------------------------------
/src/components/AccountType.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MetamaskLogo from './MetamaskLogo';
4 | import LedgerLogo from './LedgerLogo';
5 | import TrezorLogo from './TrezorLogo';
6 | import WalletConnectLogo from './WalletConnectLogo';
7 |
8 | const AccountType = ({ accountType, ...props }) => {
9 | switch (accountType) {
10 | case 'METAMASK':
11 | return ;
12 | case 'LEDGER':
13 | return ;
14 | case 'TREZOR':
15 | return ;
16 | case 'WALLETCONNECT':
17 | return ;
18 | default:
19 | return
;
20 | }
21 | };
22 |
23 | AccountType.propTypes = {
24 | accountType: PropTypes.string.isRequired,
25 | };
26 |
27 | export default AccountType;
28 |
--------------------------------------------------------------------------------
/src/references/ethereum-units.json:
--------------------------------------------------------------------------------
1 | {
2 | "basic_tx": 21000,
3 | "noether": 0,
4 | "wei": 1,
5 | "kwei": 1000,
6 | "Kwei": 1000,
7 | "babbage": 1000,
8 | "femtoether": 1000,
9 | "mwei": 1000000,
10 | "Mwei": 1000000,
11 | "lovelace": 1000000,
12 | "picoether": 1000000,
13 | "gwei": 1000000000,
14 | "Gwei": 1000000000,
15 | "shannon": 1000000000,
16 | "nanoether": 1000000000,
17 | "nano": 1000000000,
18 | "szabo": 1000000000000,
19 | "microether": 1000000000000,
20 | "micro": 1000000000000,
21 | "finney": 1000000000000000,
22 | "milliether": 1000000000000000,
23 | "milli": 1000000000000000,
24 | "ether": 1000000000000000000,
25 | "kether": 1000000000000000000000,
26 | "grand": 1000000000000000000000,
27 | "mether": 1000000000000000000000000,
28 | "gether": 1000000000000000000000000000,
29 | "tether": 1000000000000000000000000000000
30 | }
31 |
--------------------------------------------------------------------------------
/src/reducers/_modal.js:
--------------------------------------------------------------------------------
1 | // -- Constants ------------------------------------------------------------- //
2 |
3 | const MODAL_OPEN = 'modal/MODAL_OPEN';
4 | const MODAL_CLOSE = 'modal/MODAL_CLOSE';
5 |
6 | // -- Actions --------------------------------------------------------------- //
7 |
8 | export const modalOpen = (modal, props) => ({
9 | type: MODAL_OPEN,
10 | payload: modal,
11 | });
12 |
13 | export const modalClose = () => ({ type: MODAL_CLOSE });
14 |
15 | // -- Reducer --------------------------------------------------------------- //
16 | const INITIAL_STATE = {
17 | modal: '',
18 | };
19 |
20 | export default (state = INITIAL_STATE, action) => {
21 | switch (action.type) {
22 | case MODAL_OPEN:
23 | return { ...state, modal: action.payload };
24 | case MODAL_CLOSE:
25 | return { ...state, modal: '' };
26 | default:
27 | return state;
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/components/HoverWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { shadows } from '../styles';
5 |
6 | const StyledHoverWrapper = styled.div`
7 | position: relative;
8 | width: 100%;
9 | z-index: ${({ hover }) => (hover ? 20 : 0)};
10 |
11 | &:before {
12 | bottom: 0;
13 | box-shadow: ${shadows.big};
14 | content: " ";
15 | left: 0;
16 | opacity: ${({ hover }) => (hover ? 1 : 0)}
17 | position: absolute;
18 | right: 0;
19 | top: 0;
20 | }
21 | `;
22 |
23 | const HoverWrapper = ({ hover, children, ...props }) => (
24 |
25 | {children}
26 |
27 | );
28 |
29 | HoverWrapper.propTypes = {
30 | children: PropTypes.node.isRequired,
31 | hover: PropTypes.bool.isRequired,
32 | };
33 |
34 | export default HoverWrapper;
35 |
--------------------------------------------------------------------------------
/src/components/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { keyframes } from 'styled-components';
4 |
5 | const fadeIn = keyframes`
6 | 0% {
7 | opacity: 0;
8 | }
9 | 100% {
10 | opacity: 1;
11 | }
12 | `;
13 |
14 | const StyledWrapper = styled.div`
15 | will-change: transform, opacity;
16 | animation: ${fadeIn} 0.7s ease 0s normal 1;
17 | min-height: 200px;
18 | display: flex;
19 | flex-wrap: wrap;
20 | justify-content: center;
21 | align-items: ${({ center }) => (center ? `center` : `flex-start`)};
22 | `;
23 |
24 | const Wrapper = ({ children, center, ...props }) => (
25 |
26 | {children}
27 |
28 | );
29 |
30 | Wrapper.propTypes = {
31 | children: PropTypes.node.isRequired,
32 | center: PropTypes.bool,
33 | };
34 |
35 | Wrapper.defaultProps = {
36 | center: false,
37 | };
38 |
39 | export default Wrapper;
40 |
--------------------------------------------------------------------------------
/src/components/GasPanel/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import GasPriceLineBreak from '../../components/GasPriceLineBreak';
4 | import GasButton from '../../components/GasButton';
5 |
6 | import { StyledGasOptions } from './styles';
7 |
8 | // Not fully looking the same as before
9 | const GasPanel = ({ gasPriceOption, gasPrices, updateGasPrice }) => (
10 |
11 |
12 |
13 |
18 |
23 |
28 |
29 |
30 | );
31 |
32 | export default GasPanel;
33 |
--------------------------------------------------------------------------------
/src/helpers/intercom.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | (function() {
3 | var w = window;
4 | var ic = w.Intercom;
5 | if (typeof ic === 'function') {
6 | ic('reattach_activator');
7 | ic('update', intercomSettings);
8 | } else {
9 | var d = document;
10 | var i = function() {
11 | i.c(arguments);
12 | };
13 | i.q = [];
14 | i.c = function(args) {
15 | i.q.push(args);
16 | };
17 | w.Intercom = i;
18 | function l() {
19 | var s = d.createElement('script');
20 | s.type = 'text/javascript';
21 | s.async = true;
22 | s.src = `https://widget.intercom.io/widget/${
23 | process.env.NODE_ENV === 'production' ? 'j0fl7v0m' : 'k8c9ptl1'
24 | }`;
25 | var x = d.getElementsByTagName('script')[0];
26 | x.parentNode.insertBefore(s, x);
27 | }
28 | if (w.attachEvent) {
29 | w.attachEvent('onload', l);
30 | } else {
31 | w.addEventListener('load', l, false);
32 | }
33 | }
34 | })();
35 |
--------------------------------------------------------------------------------
/src/handlers/walletconnect.js:
--------------------------------------------------------------------------------
1 | import WalletConnect from 'walletconnect';
2 |
3 | const dappName = 'Balance Manager';
4 | const bridgeUrl = process.env.REACT_APP_WALLETCONNECT_BRIDGE_v7;
5 |
6 | /**
7 | * @desc init WalletConnect webConnector instance
8 | * @return {Object}
9 | */
10 | export const walletConnectGetSession = async () => {
11 | const webConnector = new WalletConnect({ bridgeUrl, dappName });
12 | await webConnector.initSession();
13 | return webConnector;
14 | };
15 |
16 | /**
17 | * @desc WalletConnect send transaction
18 | * @param {Object} transaction { from, to, data, value, gasPrice, gasLimit }
19 | * @return {String}
20 | */
21 | export const walletConnectSignTransaction = async transaction => {
22 | const webConnector = await walletConnectGetSession();
23 | if (webConnector.isConnected) {
24 | return await webConnector.sendTransaction(transaction);
25 | } else {
26 | throw new Error('WalletConnect session has expired. Please reconnect.');
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/assets/exchange-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/AssetIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import eth from '../assets/eth.svg';
5 | import erc20 from '../assets/erc20.svg';
6 |
7 | const StyledIcon = styled.img`
8 | width: ${({ size }) => `${size}px`};
9 | height: ${({ size }) => `${size}px`};
10 | `;
11 |
12 | const buildAssetSourceUrl = asset => {
13 | if (!asset) return erc20;
14 | if (asset.toUpperCase() === 'ETH') return eth;
15 | return `/tokens/images/${asset}.png`;
16 | };
17 |
18 | const AssetIcon = ({ asset, image, size }) => (
19 | (event.target.src = erc20)}
21 | size={size}
22 | src={image || buildAssetSourceUrl(asset)}
23 | />
24 | );
25 |
26 | AssetIcon.propTypes = {
27 | asset: PropTypes.string,
28 | image: PropTypes.string,
29 | size: PropTypes.number,
30 | };
31 |
32 | AssetIcon.defaultProps = {
33 | asset: null,
34 | image: '',
35 | size: 20,
36 | };
37 |
38 | export default AssetIcon;
39 |
--------------------------------------------------------------------------------
/src/components/Form.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | import { responsive } from '../styles';
6 |
7 | const StyledForm = styled.form`
8 | width: 100%;
9 | display: block;
10 |
11 | & > * {
12 | padding: 16px 16px 0;
13 | }
14 |
15 | & > *:last-child {
16 | padding: 16px;
17 | }
18 |
19 | @media screen and (${responsive.xs.max}) {
20 | & > * {
21 | padding: 16px 8px 0;
22 | }
23 |
24 | & > *:last-child {
25 | padding: 16px 8px;
26 | }
27 | }
28 | `;
29 |
30 | class Form extends Component {
31 | componentWillUnmount() {
32 | document.activeElement.blur();
33 | }
34 | render = () => {
35 | const { children, ...props } = this.props;
36 | return (
37 |
38 | {children}
39 |
40 | );
41 | };
42 | }
43 |
44 | Form.propTypes = {
45 | children: PropTypes.node.isRequired,
46 | };
47 |
48 | export default Form;
49 |
--------------------------------------------------------------------------------
/src/components/Column.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | const StyledColumn = styled.div`
6 | position: relative;
7 | width: 100%;
8 | height: ${({ spanHeight }) => (spanHeight ? '100%' : 'auto')};
9 | max-width: ${({ maxWidth }) => `${maxWidth}px`};
10 | margin: 0 auto;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: ${({ center }) => (center ? 'center' : 'flex-start')};
15 | `;
16 |
17 | const Column = ({ children, spanHeight, maxWidth, center, ...props }) => (
18 |
24 | {children}
25 |
26 | );
27 |
28 | Column.propTypes = {
29 | children: PropTypes.node.isRequired,
30 | spanHeight: PropTypes.bool,
31 | maxWidth: PropTypes.number,
32 | center: PropTypes.bool,
33 | };
34 |
35 | Column.defaultProps = {
36 | spanHeightL: false,
37 | maxWidth: 600,
38 | center: false,
39 | };
40 |
41 | export default Column;
42 |
--------------------------------------------------------------------------------
/src/assets/qr-code-transparent.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/components/TextButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { colors, fonts, transitions } from '../styles';
5 |
6 | const StyledTextButton = styled.button`
7 | transition: ${transitions.base};
8 | display: block;
9 | border: none;
10 | border-style: none;
11 | box-sizing: border-box;
12 | background: transparent;
13 | color: ${({ color }) => `rgb(${colors[color]})`};
14 | font-size: ${fonts.size.medium};
15 | font-weight: ${fonts.weight.normal};
16 | margin: 5px;
17 | cursor: pointer;
18 | will-change: transform;
19 | line-height: normal;
20 |
21 | @media (hover: hover) {
22 | &:hover {
23 | opacity: 0.6;
24 | }
25 | }
26 | `;
27 |
28 | const TextButton = ({ children, ...props }) => (
29 | {children}
30 | );
31 |
32 | TextButton.propTypes = {
33 | children: PropTypes.node.isRequired,
34 | fetching: PropTypes.bool,
35 | color: PropTypes.string,
36 | };
37 |
38 | TextButton.defaultProps = {
39 | fetching: false,
40 | color: 'darkGrey',
41 | };
42 |
43 | export default TextButton;
44 |
--------------------------------------------------------------------------------
/src/assets/caret.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
53 |
--------------------------------------------------------------------------------
/src/components/QRCodeDisplay.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import QRCode from 'qrcode';
5 |
6 | const StyledWrapper = styled.div`
7 | width: 100%;
8 | margin: 10px auto;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | `;
13 |
14 | class QRCodeDisplay extends Component {
15 | componentDidMount() {
16 | QRCode.toCanvas(
17 | this.canvas,
18 | this.props.data,
19 | {
20 | errorCorrectionLevel: this.props.errorCorrectionLevel,
21 | scale: this.props.scale,
22 | },
23 | error => {
24 | if (error) console.error(error);
25 | },
26 | );
27 | }
28 | render = () => (
29 |
30 |
32 | );
33 | }
34 |
35 | QRCodeDisplay.propTypes = {
36 | data: PropTypes.string.isRequired,
37 | errorCorrectionLevel: PropTypes.string,
38 | scale: PropTypes.number,
39 | };
40 |
41 | QRCodeDisplay.defaultProps = {
42 | errorCorrectionLevel: 'L',
43 | scale: 7,
44 | };
45 |
46 | export default QRCodeDisplay;
47 |
--------------------------------------------------------------------------------
/src/assets/walletconnect-blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/reducers/_notification.js:
--------------------------------------------------------------------------------
1 | // -- Constants ------------------------------------------------------------- //
2 | const NOTIFICATION_SHOW = 'notification/NOTIFICATION_SHOW';
3 | const NOTIFICATION_HIDE = 'notification/NOTIFICATION_HIDE';
4 |
5 | // -- Actions --------------------------------------------------------------- //
6 | let timeoutHide;
7 |
8 | export const notificationShow = (message, error = false) => dispatch => {
9 | clearTimeout(timeoutHide);
10 | dispatch({ type: NOTIFICATION_SHOW, payload: { message, error } });
11 | timeoutHide = setTimeout(() => dispatch({ type: NOTIFICATION_HIDE }), 15000);
12 | };
13 |
14 | // -- Reducer --------------------------------------------------------------- //
15 | const INITIAL_STATE = {
16 | show: false,
17 | error: false,
18 | message: '',
19 | };
20 |
21 | export default (state = INITIAL_STATE, action) => {
22 | switch (action.type) {
23 | case NOTIFICATION_SHOW:
24 | return {
25 | ...state,
26 | show: true,
27 | message: action.payload.message,
28 | error: action.payload.error,
29 | };
30 | case NOTIFICATION_HIDE:
31 | return { ...state, show: false, message: '', error: false };
32 | default:
33 | return state;
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/src/assets/opera-text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/triangles-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/Blockie.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | const StyledWrapper = styled.div`
6 | width: ${({ size }) => (size ? `${size}px` : '32px')};
7 | height: ${({ size }) => (size ? `${size}px` : '32px')};
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | border-radius: 3px;
12 | margin-right: 10px;
13 | overflow: hidden;
14 | & img {
15 | width: 100%;
16 | }
17 | `;
18 |
19 | const Blockie = ({ seed, color, bgcolor, size, scale, spotcolor }) => {
20 | const imgUrl = window.blockies
21 | .create({ seed, color, bgcolor, size, scale, spotcolor })
22 | .toDataURL();
23 | return (
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | Blockie.propTypes = {
31 | seed: PropTypes.string.isRequired,
32 | color: PropTypes.string,
33 | bgcolor: PropTypes.string,
34 | size: PropTypes.number,
35 | scale: PropTypes.number,
36 | spotcolor: PropTypes.string,
37 | };
38 |
39 | Blockie.defaultProps = {
40 | color: null,
41 | bgcolor: null,
42 | size: null,
43 | scale: null,
44 | spotcolor: null,
45 | };
46 |
47 | export default Blockie;
48 |
--------------------------------------------------------------------------------
/src/modals/ApproveTransactionModal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { capitalize, lang } from 'balance-common';
4 |
5 | import MetamaskLogo from '../../components/MetamaskLogo';
6 | import LedgerLogo from '../../components/LedgerLogo';
7 | import TrezorLogo from '../../components/TrezorLogo';
8 |
9 | import Button from '../../components/Button';
10 |
11 | import { StyledApproveTransaction } from './styles';
12 | import { StyledParagraph, StyledActions } from '../modalStyles';
13 |
14 | const ApproveTransactionModal = ({ accountType, onClose }) => (
15 |
16 | {(() => {
17 | switch (accountType) {
18 | case 'METAMASK':
19 | return ;
20 | case 'LEDGER':
21 | return ;
22 | case 'TREZOR':
23 | return ;
24 | default:
25 | return ;
26 | }
27 | })()}
28 |
29 |
30 | {lang.t('modal.approve_tx', {
31 | walletType: capitalize(accountType),
32 | })}
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 |
41 | export default ApproveTransactionModal;
42 |
--------------------------------------------------------------------------------
/src/assets/balances-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/ClickOutside.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class ClickOutside extends Component {
5 | static propTypes = {
6 | onClickOutside: PropTypes.func.isRequired,
7 | };
8 |
9 | constructor(props) {
10 | super(props);
11 | this.isTouch = false;
12 | }
13 |
14 | handleContainerRef = ref => {
15 | this.containerRef = ref;
16 | };
17 |
18 | componentDidMount() {
19 | document.addEventListener('touchend', this.handle, true);
20 | document.addEventListener('click', this.handle, true);
21 | }
22 |
23 | componentWillUnmount() {
24 | document.removeEventListener('touchend', this.handle, true);
25 | document.removeEventListener('click', this.handle, true);
26 | }
27 |
28 | handle = event => {
29 | if (event.type === 'touchend') this.isTouch = true;
30 | if (event.type === 'click' && this.isTouch) return;
31 | const { onClickOutside } = this.props;
32 | const el = this.containerRef;
33 | if (!el.contains(event.target)) onClickOutside(event);
34 | };
35 |
36 | render() {
37 | const { children, onClickOutside, ...props } = this.props;
38 | return (
39 |
40 | {children}
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/assets/caret-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/opera-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/blockies.min.js:
--------------------------------------------------------------------------------
1 | !function(){function e(e){for(var o=0;o>19^e^e>>8,(c[3]>>>0)/(1<<31>>>0)}function r(){var e=Math.floor(360*o()),r=60*o()+40+"%",t=25*(o()+o()+o()+o())+"%",l="hsl("+e+","+r+","+t+")";return l}function t(e){for(var r=e,t=e,l=Math.ceil(r/2),n=r-l,a=[],c=0;t>c;c++){for(var i=[],f=0;l>f;f++)i[f]=Math.floor(2.3*o());var s=i.slice(0,n);s.reverse(),i=i.concat(s);for(var h=0;h (
15 |
16 |
17 |
18 | {`Success`}
19 |
20 |
21 |
22 |
23 | {`${lang.t('modal.tx_hash')}:`}
24 |
25 | {` ${txHash}`}
26 |
27 |
28 |
29 |
36 | {lang.t('modal.tx_verify')}
37 |
38 |
39 |
40 |
41 |
44 |
45 |
46 | );
47 |
48 | export default SuccessModal;
49 |
--------------------------------------------------------------------------------
/src/assets/gbp.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './helpers/intercom';
4 | import { injectGlobal } from 'styled-components';
5 | import { globalStyles } from './styles';
6 | import { bootIntercom } from './helpers/bootIntercom';
7 | import Root from './Root';
8 | import Storage from '@devshack/react-native-storage';
9 | import { commonStorage, lang, resources } from 'balance-common';
10 |
11 | const storage = new Storage({
12 | size: 1000,
13 | storageBackend: window.localStorage,
14 | defaultExpires: null,
15 | enableCache: true,
16 | });
17 |
18 | window.storage = storage;
19 |
20 | // Languages (i18n)
21 | lang.init({
22 | lng: 'en',
23 | fallbackLng: 'en',
24 | debug: process.env.NODE_ENV === 'development',
25 | resources,
26 | });
27 |
28 | commonStorage
29 | .getLanguage()
30 | .then(language => {
31 | lang.init({
32 | lng: language,
33 | fallbackLng: 'en',
34 | debug: process.env.NODE_ENV === 'development',
35 | resources,
36 | });
37 | })
38 | .catch(error => {
39 | lang.init({
40 | lng: 'en',
41 | fallbackLng: 'en',
42 | debug: process.env.NODE_ENV === 'development',
43 | resources,
44 | });
45 | });
46 |
47 | // eslint-disable-next-line
48 | injectGlobal`${globalStyles}`;
49 |
50 | // Intercom
51 | bootIntercom();
52 |
53 | ReactDOM.render(, document.getElementById('root'));
54 |
--------------------------------------------------------------------------------
/src/assets/trustwallet-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/LineBreak.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { colors } from '../styles';
5 |
6 | const StyledLineBreakWrapper = styled.div`
7 | position: relative;
8 | width: 100% !important;
9 | padding: 0 !important;
10 | margin: 0 !important;
11 | margin-top: ${({ noMargin }) =>
12 | noMargin ? `0 !important` : `25px !important`};
13 | border-top: 2px solid rgb(241, 242, 246);
14 | `;
15 |
16 | const StyledLineBreakFiller = styled.div`
17 | transition: 0.32s cubic-bezier(0.77, 0, 0.175, 1);
18 | position: absolute;
19 | left: 0;
20 | bottom: 0;
21 | width: 100%;
22 | transform-origin: 0;
23 | transform: ${({ percentage }) => `scale3d(${percentage / 100}, 1, 1)`};
24 | border-top: ${({ color }) =>
25 | color ? `2px solid rgb(${colors[color]})` : `2px solid rgb(241, 242, 246)`};
26 | `;
27 |
28 | const LineBreak = ({ noMargin, color, percentage, ...props }) => (
29 |
30 |
31 |
32 | );
33 |
34 | LineBreak.propTypes = {
35 | noMargin: PropTypes.bool,
36 | color: PropTypes.string,
37 | percentage: PropTypes.number,
38 | };
39 |
40 | LineBreak.defaultProps = {
41 | noMargin: false,
42 | color: '',
43 | percentage: 0,
44 | };
45 |
46 | export default LineBreak;
47 |
--------------------------------------------------------------------------------
/src/assets/btc.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/eur.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Balance Manager
2 |
3 |
4 |
5 | #### Ethereum Wallet Manager for MetaMask, Ledger, Trezor and WalletConnect
6 |
7 | ### Getting started
8 |
9 | We use submodules to sync [Balance Token image assets](https://github.com/balance-io/tokens) into this project
10 | After cloning `balance-manager`, run this command to install all `git submodules`
11 |
12 | ```
13 | git submodule update --init --recursive
14 | ```
15 |
16 | Then install all project dependencies
17 |
18 | ```
19 | yarn
20 | - or -
21 | npm install
22 | ```
23 |
24 | ## How to run Manager
25 |
26 | For development
27 |
28 | ```
29 | yarn start
30 | - or -
31 | npm run start
32 | ```
33 |
34 | For production
35 |
36 | ```
37 | yarn build
38 | - or -
39 | npm run build
40 | ```
41 |
42 | ## Want to help us?
43 |
44 | We really appreciate any time you can spare. We need help with design, engineering, translation and security audit.
45 |
46 | Issues: https://github.com/balance-io/balance-manager/issues
47 |
48 | Community: https://spectrum.chat/balance/manager
49 |
50 | Stuck on something? Got questions? Want to chat privately? Send @ricburton a Direct Message (DM) on Twitter at any time: twitter.com/ricburton
51 |
52 | ## How to contribute to Manager
53 |
54 | Always clone or fork from the `latest` branch.
55 | We are going to work on making this process as simple as possible.
56 | At this stage we would encourage you to comment on issues you want to work on and reach out to us over Spectrum or Twitter.
57 |
--------------------------------------------------------------------------------
/src/Router.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Route, Switch, withRouter } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 | import Homepage from './pages';
6 | import Wallet from './pages/Wallet';
7 | import Metamask from './pages/Metamask';
8 | import Ledger from './pages/Ledger';
9 | import Trezor from './pages/Trezor';
10 | import NotFound from './pages/NotFound';
11 | import { warningOnline, warningOffline } from './reducers/_warning';
12 |
13 | class Router extends Component {
14 | componentDidMount() {
15 | window.browserHistory = this.context.router.history;
16 | window.onoffline = () => this.props.warningOffline();
17 | window.ononline = () => this.props.warningOnline();
18 | }
19 |
20 | render = () => (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | Router.contextTypes = {
33 | router: PropTypes.object.isRequired,
34 | store: PropTypes.object.isRequired,
35 | email: PropTypes.string,
36 | signup: PropTypes.any,
37 | };
38 |
39 | const reduxProps = ({ account }) => ({
40 | language: account.language,
41 | });
42 |
43 | export default withRouter(
44 | connect(
45 | reduxProps,
46 | {
47 | warningOffline,
48 | warningOnline,
49 | },
50 | )(Router),
51 | );
52 |
--------------------------------------------------------------------------------
/src/components/ToggleIndicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import balancesTabIcon from '../assets/balances-tab.svg';
5 | import circle from '../assets/circle.svg';
6 | import { colors, fonts } from '../styles';
7 |
8 | const StyledToggleIndicator = styled.div`
9 | display: flex;
10 | align-items: center;
11 | cursor: pointer;
12 |
13 | &:not(:last-child) {
14 | padding-right: 16px;
15 | }
16 |
17 | @media (hover: hover) {
18 | &:hover span {
19 | color: rgb(${colors.darkGrey});
20 | }
21 | }
22 | `;
23 |
24 | const StyledToggleIndicatorIcon = styled.span`
25 | display: inline-flex;
26 | height: 16px;
27 | width: 16px;
28 | margin-right: 8px;
29 | mask: ${({ show }) =>
30 | show
31 | ? `url(${circle}) center no-repeat`
32 | : `url(${balancesTabIcon}) center no-repeat`};
33 | background-color: rgb(${colors.grey});
34 | `;
35 |
36 | const StyledToggleIndicatorText = styled.p`
37 | display: inline-flex;
38 | text-align: left;
39 | font-family: ${fonts.family.SFProText};
40 | font-weight: ${fonts.weight.medium};
41 | font-size: 13px;
42 | color: rgb(${colors.grey});
43 | `;
44 |
45 | const ToggleIndicator = ({ children, show, ...props }) => (
46 |
47 |
48 | {children}
49 |
50 | );
51 |
52 | ToggleIndicator.propTypes = {
53 | show: PropTypes.bool,
54 | };
55 |
56 | ToggleIndicator.defaultProps = {
57 | show: false,
58 | };
59 |
60 | export default ToggleIndicator;
61 |
--------------------------------------------------------------------------------
/src/assets/firefox-text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Notification.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import PropTypes from 'prop-types';
4 | import { connect } from 'react-redux';
5 | import { colors, shadows, responsive, transitions } from '../styles';
6 |
7 | const StyledNotification = styled.div`
8 | position: fixed;
9 | z-index: 20;
10 | width: calc(100% - 20px);
11 | max-width: 400px;
12 | top: 0;
13 | right: 0;
14 | margin: 10px;
15 | text-align: center;
16 | padding: 15px 20px;
17 | border-radius: 8px;
18 | text-align: center;
19 | transition: ${transitions.base};
20 | background: rgb(${colors.white});
21 | color: ${({ error }) =>
22 | error ? `rgb(${colors.red})` : `rgb(${colors.dark})`};
23 | box-shadow: ${shadows.medium};
24 | transform: ${({ show }) =>
25 | show ? 'translate3d(0, 0, 0)' : 'translate3d(0, -1000px, 0);'};
26 |
27 | @media screen and (${responsive.sm.max}) {
28 | top: auto;
29 | left: 0;
30 | bottom: 0;
31 | margin: 0 auto;
32 | transform: ${({ show }) =>
33 | show ? 'translate3d(0, 0, 0)' : 'translate3d(0, 1000px, 0);'};
34 | }
35 | `;
36 |
37 | const Notification = ({ show, error, message, ...props }) => (
38 |
39 | {message}
40 |
41 | );
42 |
43 | Notification.propTypes = {
44 | show: PropTypes.bool.isRequired,
45 | error: PropTypes.bool.isRequired,
46 | message: PropTypes.string.isRequired,
47 | };
48 |
49 | const reduxProps = ({ notification }) => ({
50 | error: notification.error,
51 | show: notification.show,
52 | message: notification.message,
53 | });
54 |
55 | export default connect(
56 | reduxProps,
57 | null,
58 | )(Notification);
59 |
--------------------------------------------------------------------------------
/src/assets/convert-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/usd.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/chrome-text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Wallet.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 | import BaseLayout from '../layouts/base';
6 | import Account from '../views/Account';
7 | import Card from '../components/Card';
8 | import { lang } from 'balance-common';
9 | import { fonts, colors } from '../styles';
10 |
11 | const StyledWrapper = styled.div`
12 | width: 100%;
13 | `;
14 |
15 | const StyledMessage = styled.div`
16 | display: flex;
17 | align-items: center;
18 | justify-content: center;
19 | color: rgb(${colors.grey});
20 | font-weight: ${fonts.weight.medium};
21 | `;
22 |
23 | class Wallet extends Component {
24 | componentDidMount() {
25 | if (!this.props.accountAddress) {
26 | console.log('wallet page does not have account address');
27 | this.props.history.push('/');
28 | }
29 | }
30 |
31 | render = () => (
32 |
33 |
34 | {this.props.fetching || this.props.accountAddress ? (
35 |
36 | ) : (
37 |
38 |
39 | {lang.t('message.walletconnect_not_unlocked')}
40 |
41 |
42 | )}
43 |
44 |
45 | );
46 | }
47 |
48 | Wallet.propTypes = {
49 | accountAddress: PropTypes.string,
50 | fetching: PropTypes.bool.isRequired,
51 | match: PropTypes.object.isRequired,
52 | };
53 |
54 | Wallet.defaultProps = {
55 | accountAddress: null,
56 | };
57 |
58 | const reduxProps = ({ walletconnect }) => ({
59 | fetching: walletconnect.fetching,
60 | accountAddress: walletconnect.accountAddress,
61 | });
62 |
63 | export default connect(
64 | reduxProps,
65 | null,
66 | )(Wallet);
67 |
--------------------------------------------------------------------------------
/src/components/Loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { keyframes } from 'styled-components';
4 | import { fonts, colors } from '../styles';
5 |
6 | const load = keyframes`
7 | 0% {
8 | transform: rotate(0deg);
9 | }
10 | 100% {
11 | transform: rotate(360deg);
12 | }
13 | `;
14 |
15 | const StyledLoader = styled.div`
16 | position: relative;
17 | font-size: ${fonts.size.tiny};
18 | margin: 0 auto;
19 | text-indent: -9999em;
20 | width: ${({ size }) => `${size}px`};
21 | height: ${({ size }) => `${size}px`};
22 | border-radius: 50%;
23 | background ${({ color }) => `rgb(${colors[color]})`};
24 | background: ${({ background, color }) =>
25 | `linear-gradient(to right, rgb(${colors[color]}) 10%, rgba(${
26 | colors[background]
27 | }, 0) 42%)`};
28 | animation: ${load} 1s infinite linear;
29 | transform: translateZ(0);
30 |
31 | &:before {
32 | width: 50%;
33 | height: 50%;
34 | background ${({ color }) => `rgb(${colors[color]})`};
35 | border-radius: 100% 0 0 0;
36 | position: absolute;
37 | top: 0;
38 | left: 0;
39 | content: '';
40 | }
41 |
42 | &:after {
43 | background: ${({ background }) => `rgb(${colors[background]})`};
44 | width: 75%;
45 | height: 75%;
46 | border-radius: 50%;
47 | content: '';
48 | margin: auto;
49 | position: absolute;
50 | top: 0;
51 | left: 0;
52 | bottom: 0;
53 | right: 0;
54 | }
55 | `;
56 |
57 | const Loader = ({ size, color, background, ...props }) => (
58 |
59 | );
60 |
61 | Loader.propTypes = {
62 | size: PropTypes.number,
63 | color: PropTypes.string,
64 | background: PropTypes.string,
65 | };
66 |
67 | Loader.defaultProps = {
68 | size: 50,
69 | color: 'white',
70 | background: 'dark',
71 | };
72 |
73 | export default Loader;
74 |
--------------------------------------------------------------------------------
/src/assets/mail.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/brave-text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Balance Manager
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/assets/interactions-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/QRCodeReader.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import QrReader from 'react-qr-reader';
5 | import Column from './Column';
6 | import cross from '../assets/cross.svg';
7 | import { colors } from '../styles';
8 |
9 | const StyledWrapper = styled.div`
10 | position: fixed;
11 | top: 0;
12 | left: 0;
13 | right: 0;
14 | bottom: 0;
15 | z-index: 5;
16 | margin: 0 auto !important;
17 | background: rgb(${colors.black});
18 | `;
19 |
20 | const StyledClose = styled.img`
21 | position: absolute;
22 | z-index: 10;
23 | top: 15px;
24 | right: 15px;
25 | width: 30px;
26 | height: 30px;
27 | mask: url(${cross}) center no-repeat;
28 | mask-size: 95%;
29 | background-color: rgb(${colors.grey});
30 |
31 | @media (hover: hover) {
32 | &:hover {
33 | opacity: 0.6;
34 | }
35 | }
36 | `;
37 |
38 | class QRCodeReader extends Component {
39 | state = {
40 | delay: 500,
41 | };
42 | stopRecording = () => this.setState({ delay: false });
43 | handleScan = data => {
44 | if (data) {
45 | const validate = this.props.onValidate(data);
46 | if (validate.result) {
47 | this.stopRecording();
48 | this.props.onScan(validate.data);
49 | } else {
50 | validate.onError();
51 | }
52 | }
53 | };
54 | handleError = error => {
55 | console.error(error);
56 | this.props.onError(error);
57 | };
58 | onClose = () => {
59 | this.stopRecording();
60 | this.props.onClose();
61 | };
62 | componentWillUnmount() {
63 | this.stopRecording();
64 | }
65 | render() {
66 | return (
67 |
68 |
69 |
70 |
76 |
77 |
78 | );
79 | }
80 | }
81 |
82 | QRCodeReader.propTypes = {
83 | onScan: PropTypes.func.isRequired,
84 | onError: PropTypes.func.isRequired,
85 | onClose: PropTypes.func.isRequired,
86 | onValidate: PropTypes.func.isRequired,
87 | };
88 |
89 | export default QRCodeReader;
90 |
--------------------------------------------------------------------------------
/src/modals/ReceiveModal/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 |
6 | import { capitalize, lang } from 'balance-common';
7 |
8 | import Card from '../../components/Card';
9 | import Button from '../../components/Button';
10 |
11 | import arrowUp from '../../assets/arrow-up.svg';
12 |
13 | import { modalClose } from '../../reducers/_modal';
14 |
15 | import {
16 | StyledContainer,
17 | StyledQRCodeDisplay,
18 | StyledIcon,
19 | StyledSubTitle,
20 | StyledJustifyContent,
21 | StyledCopyToClipboard,
22 | } from '../modalStyles';
23 |
24 | const reduxProps = ({ account }) => ({
25 | accountAddress: account.accountAddress,
26 | accountType: account.accountType,
27 | });
28 |
29 | const StyledCardContainer = styled.div`
30 | max-width: 440px;
31 | width: 100%;
32 | `;
33 |
34 | class ReceiveModal extends Component {
35 | static propTypes = {
36 | modalClose: PropTypes.func.isRequired,
37 | accountAddress: PropTypes.string.isRequired,
38 | accountType: PropTypes.string.isRequired,
39 | };
40 |
41 | onClose = () => {
42 | this.props.modalClose();
43 | };
44 |
45 | render = () => {
46 | return (
47 |
48 |
49 |
50 |
51 |
52 |
53 | {lang.t('modal.receive_title', {
54 | walletName: capitalize(
55 | `${this.props.accountType}${lang.t(
56 | 'modal.default_wallet',
57 | )}`,
58 | ),
59 | })}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | );
69 | };
70 | }
71 |
72 | export default connect(
73 | reduxProps,
74 | {
75 | modalClose,
76 | },
77 | )(ReceiveModal);
78 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { lang } from 'balance-common';
4 | import OpenSeaLogo from '../assets/opensea-logo.svg';
5 | import { fonts, responsive } from '../styles';
6 |
7 | const Container = styled.div`
8 | border-top: solid #e1e4e8 1px;
9 | display: flex;
10 |
11 | @media screen and (${responsive.sm.max}) {
12 | flex-direction: column-reverse;
13 | }
14 | `;
15 |
16 | const ContainerLogo = styled.div`
17 | border-left: solid #e1e4e8 1px;
18 | display: flex;
19 | flex-basis: 25%;
20 | flex-direction: column;
21 | justify-content: center;
22 | padding: 20px;
23 |
24 | @media screen and (${responsive.sm.max}) {
25 | border-left: none;
26 | flex-basis: 100%;
27 | padding-bottom: 0;
28 | }
29 | `;
30 |
31 | const TextField = styled.div`
32 | padding: 20px;
33 | width: 75%;
34 |
35 | @media screen and (${responsive.sm.max}) {
36 | width: 100%;
37 | }
38 | `;
39 |
40 | const Header = styled.p`
41 | font-size: ${fonts.size.large};
42 | font-weight: 500;
43 | margin-bottom: 5px;
44 | `;
45 |
46 | const Text = styled.p`
47 | font-size: ${fonts.size.medium};
48 | line-height: 24px;
49 | `;
50 |
51 | const Link = styled.a`
52 | color: #6783e0;
53 | `;
54 |
55 | const LinkFat = styled.a`
56 | color: #6783e0;
57 | font-weight: bold;
58 | `;
59 |
60 | const PowerUp = styled.div`
61 | margin: 0;
62 | font-weight: 500;
63 | color: #91939f;
64 | padding-bottom: 10px;
65 | display: inline;
66 | `;
67 |
68 | const Footer = () => (
69 |
70 |
71 | {lang.t('message.opensea_header')}
72 |
73 |
78 | OpenSea
79 |
80 | {lang.t('message.opensea_footer')}
81 |
86 | {lang.t('button.learn_more')}
87 |
88 |
89 |
90 |
91 | {lang.t('message.power_by')}
92 |
93 |
94 |
95 | );
96 |
97 | export default Footer;
98 |
--------------------------------------------------------------------------------
/src/views/Account/AccountUniqueTokens.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import styled from 'styled-components';
5 | import { lang } from 'balance-common';
6 | import Card from '../../components/Card';
7 | import Footer from '../../components/Footer';
8 | import UniqueToken from '../../components/UniqueToken';
9 | import { colors, fonts } from '../../styles';
10 |
11 | const UniqueTokensContainer = styled.div`
12 | background: #ffffff;
13 | `;
14 |
15 | const StyledContainer = styled.div`
16 | background: #f7f8fc;
17 | border: 2px solid #e2e2e2;
18 | border-radius: 15px;
19 | padding: 20px 10px 0px;
20 | margin: 15px;
21 | `;
22 |
23 | const StyledCard = styled(Card)`
24 | box-shadow: none;
25 | padding: 0 16px;
26 | `;
27 |
28 | const StyledMessage = styled.div`
29 | display: flex;
30 | align-items: center;
31 | justify-content: center;
32 | color: rgb(${colors.grey});
33 | font-weight: ${fonts.weight.medium};
34 | `;
35 |
36 | class AccountUniqueTokens extends Component {
37 | render() {
38 | const { uniqueTokens } = this.props;
39 | if (!uniqueTokens) return null;
40 |
41 | return !!uniqueTokens.length ? (
42 | !this.props.fetchingUniqueTokens ? (
43 |
44 |
45 | {uniqueTokens.map(token => (
46 |
47 | ))}
48 |
49 |
50 |
51 | ) : (
52 |
53 | {lang.t('message.failed_request')}
54 |
55 | )
56 | ) : (
57 |
58 | {lang.t('message.no_unique_tokens')}
59 |
60 | );
61 | }
62 | }
63 |
64 | AccountUniqueTokens.propTypes = {
65 | uniqueTokens: PropTypes.array.isRequired,
66 | fetchingUniqueTokens: PropTypes.bool.isRequired,
67 | };
68 | const reduxProps = ({ account }) => ({
69 | uniqueTokens: account.uniqueTokens,
70 | fetchingUniqueTokens: account.fetchingUniqueTokens,
71 | });
72 |
73 | export default connect(
74 | reduxProps,
75 | null,
76 | )(AccountUniqueTokens);
77 |
--------------------------------------------------------------------------------
/src/components/Warning.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled, { keyframes } from 'styled-components';
5 | import { colors, fonts, responsive } from '../styles';
6 |
7 | const slideDown = keyframes`
8 | 0% { transform: translate3d(0, -100%, 0); }
9 | 100% { transform: translate3d(0, 0, 0); }
10 | `;
11 |
12 | const StyledWrapper = styled.div`
13 | position: absolute;
14 | top: 0;
15 | left: 0;
16 | z-index: 2;
17 | opacity: ${({ show }) => (show ? 1 : 0)};
18 | visibility: ${({ show }) => (show ? 'visible' : 'hidden')};
19 | pointer-events: ${({ show }) => (show ? 'auto' : 'none')};
20 | margin-bottom: 5px;
21 | width: 100vw;
22 | & > div {
23 | border-radius: 0;
24 | }
25 | & > div:nth-child(n + 2) {
26 | margin-top: 0;
27 | }
28 | @media screen and (${responsive.sm.max}) {
29 | & > div {
30 | font-size: ${fonts.size.h6};
31 | }
32 | }
33 | `;
34 |
35 | const StyledWarning = styled.div`
36 | width: 100%;
37 | height: 32px;
38 | font-size: ${fonts.size.h6};
39 | margin: 0 auto;
40 | position: relative;
41 | background: ${({ color }) => `rgb(${colors[color]})`};
42 | color: rgb(${colors.white});
43 | display: flex;
44 | text-align: center;
45 | align-items: center;
46 | justify-content: center;
47 | cursor: pointer;
48 | z-index: ${({ idx }) => (idx ? `${idx}` : '0')};
49 | animation: ${slideDown} 0.3s ease 1;
50 | @media screen and (${responsive.md.max}) {
51 | max-width: none;
52 | animation: none;
53 | }
54 | `;
55 |
56 | class Warning extends Component {
57 | render() {
58 | return (
59 |
60 | {!!this.props.active.length
61 | ? this.props.active.map((warning, idx, arr) => (
62 | warning.action()}
67 | >
68 | {warning.message}
69 |
70 | ))
71 | : null}
72 |
73 | );
74 | }
75 | }
76 |
77 | Warning.propTypes = {
78 | show: PropTypes.bool.isRequired,
79 | active: PropTypes.array.isRequired,
80 | };
81 |
82 | const reduxProps = ({ warning }) => ({
83 | show: warning.show,
84 | active: warning.active,
85 | });
86 |
87 | export default connect(
88 | reduxProps,
89 | null,
90 | )(Warning);
91 |
--------------------------------------------------------------------------------
/src/reducers/_zrxinstant.js:
--------------------------------------------------------------------------------
1 | import {
2 | ledgerEthereumBrowserClientFactoryAsync as ledgerEthereumClientFactoryAsync,
3 | LedgerSubprovider,
4 | RPCSubprovider,
5 | Web3ProviderEngine,
6 | } from '@0x/subproviders';
7 |
8 | const balanceManagerZrxInstantAddress =
9 | process.env.REACT_APP_ZRX_INSTANT_ADDRESS;
10 | const balanceManagerZrxInstantRelayer =
11 | process.env.REACT_APP_ZRX_INSTANT_RELAYER;
12 |
13 | // -- Constants ------------------------------------------------------------- //
14 | const ZRX_INSTANT_RENDER_MODAL_REQUEST =
15 | 'zrxinstant/ZRX_INSTANT_RENDER_MODAL_REQUEST';
16 | const ZRX_INSTANT_RENDER_MODAL_SUCCESS =
17 | 'zrxinstant/ZRX_INSTANT_RENDER_MODAL_SUCCESS';
18 |
19 | // -- Actions --------------------------------------------------------------- //
20 | export const zrxInstantInit = () => (dispatch, getState) => {
21 | dispatch({ type: ZRX_INSTANT_RENDER_MODAL_REQUEST });
22 | const { accountType } = getState().account;
23 | const providerEngine = new Web3ProviderEngine();
24 | let provider = null;
25 | let walletDisplayName = 'Metamask';
26 | switch (accountType) {
27 | case 'LEDGER':
28 | walletDisplayName = 'Ledger';
29 | const ledgerSubprovider = new LedgerSubprovider({
30 | networkId: 1,
31 | ledgerEthereumClientFactoryAsync,
32 | });
33 | providerEngine.addProvider(ledgerSubprovider);
34 | providerEngine.addProvider(
35 | new RPCSubprovider('https://mainnet.infura.io/'),
36 | );
37 | break;
38 | default:
39 | provider = window.ethereum || window.web3.currentProvider;
40 | break;
41 | }
42 | providerEngine.start();
43 | window.zeroExInstant.render({
44 | affiliateInfo: {
45 | feeRecipient: balanceManagerZrxInstantAddress,
46 | feePercentage: 0.01,
47 | },
48 | orderSource: balanceManagerZrxInstantRelayer,
49 | provider: provider || providerEngine,
50 | walletDisplayName,
51 | });
52 | dispatch({ type: ZRX_INSTANT_RENDER_MODAL_SUCCESS });
53 | };
54 |
55 | // -- Reducer --------------------------------------------------------------- //
56 | const INITIAL_STATE = {
57 | fetching: false,
58 | };
59 |
60 | export default (state = INITIAL_STATE, action) => {
61 | switch (action.type) {
62 | case ZRX_INSTANT_RENDER_MODAL_REQUEST:
63 | return {
64 | ...state,
65 | fetching: true,
66 | };
67 | case ZRX_INSTANT_RENDER_MODAL_SUCCESS:
68 | return {
69 | ...state,
70 | fetching: false,
71 | };
72 | default:
73 | return state;
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/src/components/CopyToClipboard/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { lang } from 'balance-common';
5 | import clipboardIcon from '../../assets/clipboard.png';
6 | import { toChecksumAddress } from 'balance-common';
7 | import { notificationShow } from '../../reducers/_notification';
8 | import {
9 | StyledCopyToClipboard,
10 | StyledInput,
11 | StyledText,
12 | StyledIcon,
13 | } from './styles';
14 |
15 | let timeout = null;
16 |
17 | class CopyToClipboard extends Component {
18 | copyToCopyToClipboard = ({ target }) => {
19 | clearTimeout(timeout);
20 | target.select();
21 | document.execCommand('Copy');
22 | target.blur();
23 | this.props.notificationShow(
24 | lang.t('notification.info.address_copied_to_clipboard'),
25 | );
26 | };
27 | simulateCopyToClipboard = () => {
28 | const str = this.props.text;
29 | const element = document.createElement('textarea');
30 | element.value = str;
31 | document.body.appendChild(element);
32 | element.select();
33 | document.execCommand('copy');
34 | document.body.removeChild(element);
35 | this.props.notificationShow(
36 | lang.t('notification.info.address_copied_to_clipboard'),
37 | );
38 | };
39 | render() {
40 | let {
41 | displayIcon,
42 | iconOnHover,
43 | isTopAddress,
44 | notificationShow,
45 | text,
46 | ...props
47 | } = this.props;
48 |
49 | text = toChecksumAddress(text);
50 |
51 | return (
52 |
53 | {}}
58 | onClick={this.copyToCopyToClipboard}
59 | />
60 | {lang.t('message.click_to_copy_to_clipboard')}
61 |
67 |
68 | );
69 | }
70 | }
71 |
72 | CopyToClipboard.propTypes = {
73 | notificationShow: PropTypes.func.isRequired,
74 | text: PropTypes.string.isRequired,
75 | iconOnHover: PropTypes.bool,
76 | };
77 |
78 | CopyToClipboard.defaultProps = {
79 | displayIcon: false,
80 | iconOnHover: false,
81 | isTopAddress: false,
82 | };
83 |
84 | export default connect(
85 | null,
86 | { notificationShow },
87 | )(CopyToClipboard);
88 |
--------------------------------------------------------------------------------
/src/handlers/trezor-eth.js:
--------------------------------------------------------------------------------
1 | import EthereumTx from 'ethereumjs-tx';
2 | import ethereumNetworks from '../references/ethereum-networks.json';
3 | import { lang } from 'balance-common';
4 |
5 | const HDKey = require('ethereumjs-wallet/hdkey');
6 |
7 | export let trezorEthInstance = {
8 | length: 10,
9 | accounts: [],
10 | networkId: 1,
11 | basePath: `m/44'/60'/0'/0`,
12 | hdkey: '',
13 | };
14 |
15 | export const trezorEthInit = (network = 'mainnet') => {
16 | const networkId = ethereumNetworks[network].id;
17 | const basePath = `m/44'/${networkId === 1 ? '60' : '1'}'/0'/0`;
18 | return new Promise((resolve, reject) => {
19 | window.TrezorConnect.getXPubKey(basePath, response => {
20 | if (response.success) {
21 | trezorEthInstance.networkId = networkId;
22 | trezorEthInstance.hdkey = HDKey.fromExtendedKey(response.xpubkey);
23 | trezorEthInstance.basePath = basePath;
24 | resolve(trezorEthInstance);
25 | } else {
26 | reject(response.error);
27 | }
28 | });
29 | });
30 | };
31 |
32 | export const trezorEthAccounts = () => {
33 | let accounts = [];
34 | for (let i = 0; i < trezorEthInstance.length; i++) {
35 | let account = {};
36 | const childKey = trezorEthInstance.hdkey.deriveChild(i);
37 | const wallet = childKey.getWallet();
38 | const address = wallet.getAddressString();
39 | account.address = address;
40 | account.path = `${trezorEthInstance.basePath}/${i}`;
41 | accounts.push(account);
42 | }
43 | trezorEthInstance.accounts = accounts;
44 | return Promise.resolve(accounts);
45 | };
46 |
47 | export const trezorEthSignTransaction = async tx => {
48 | const account = trezorEthInstance.accounts.filter(
49 | account => account.address.toLowerCase() === tx.from,
50 | )[0];
51 | const { r, s, v } = await new Promise(resolve =>
52 | window.TrezorConnect.ethereumSignTx(
53 | account.path,
54 | ...[tx.nonce, tx.gasPrice, tx.gasLimit, tx.to, tx.value, tx.data].map(
55 | hex => {
56 | const cleaned = hex.replace('0x', '').toLowerCase();
57 | return cleaned.length % 2 !== 0 ? `0${cleaned}` : cleaned;
58 | },
59 | ),
60 | trezorEthInstance.networkId,
61 | resolve,
62 | ),
63 | );
64 |
65 | try {
66 | const str = `0x${new EthereumTx({
67 | ...tx,
68 | r: `0x${r}`,
69 | s: `0x${s}`,
70 | v: `0x${v.toString(16)}`,
71 | })
72 | .serialize()
73 | .toString('hex')}`;
74 | return str;
75 | } catch (error) {
76 | throw new Error(lang.t('message.failed_trezor_popup_blocked'));
77 | }
78 | };
79 |
--------------------------------------------------------------------------------
/src/modals/WalletConnectModal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 | import { lang } from 'balance-common';
6 | import Card from '../components/Card';
7 | import Loader from '../components/Loader';
8 | import QRCodeDisplay from '../components/QRCodeDisplay';
9 | import Button from '../components/Button';
10 | import { modalClose } from '../reducers/_modal';
11 | import { walletConnectClearFields } from '../reducers/_walletconnect';
12 |
13 | const StyledCard = styled(Card)`
14 | margin: 0 16px;
15 | max-height: 500px;
16 | `;
17 |
18 | const StyledContainer = styled.div`
19 | padding: 0 0 16px;
20 | `;
21 |
22 | const StyledQRCodeWrapper = styled.div`
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | `;
27 |
28 | const StyledQRCodeDisplay = styled(QRCodeDisplay)`
29 | margin: 0 auto;
30 | `;
31 |
32 | const StyledCenter = styled.div`
33 | text-align: center;
34 | `;
35 |
36 | class WalletConnectModal extends Component {
37 | constructor(props) {
38 | super(props);
39 | this.isSmallScreen = window.innerWidth < 530;
40 | }
41 | onClose = () => {
42 | this.props.walletConnectClearFields();
43 | this.props.modalClose();
44 | };
45 |
46 | render = () => {
47 | const { qrcode } = this.props;
48 | return (
49 |
50 |
51 |
52 | {qrcode ? (
53 |
57 | ) : (
58 |
59 | )}
60 |
61 |
62 |
70 |
71 |
72 |
73 | );
74 | };
75 | }
76 |
77 | WalletConnectModal.propTypes = {
78 | modalClose: PropTypes.func.isRequired,
79 | };
80 |
81 | const reduxProps = ({ modal, walletconnect }) => ({
82 | qrcode: walletconnect.qrcode,
83 | });
84 |
85 | export default connect(
86 | reduxProps,
87 | {
88 | modalClose,
89 | walletConnectClearFields,
90 | },
91 | )(WalletConnectModal);
92 |
--------------------------------------------------------------------------------
/src/components/ReminderRibbon.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import {
5 | getSupressReminderRibbon,
6 | saveSupressReminderRibbon,
7 | } from '../handlers/localstorage';
8 | import cross from '../assets/cross.svg';
9 | import { colors } from '../styles';
10 |
11 | const StyledReminderRibbon = styled.div`
12 | will-change: height;
13 | height: ${({ show }) => (show ? '40px' : 0)};
14 | overflow: hidden;
15 | transition: height 0.5s ease;
16 | background: #647fe6;
17 | `;
18 |
19 | const StyledReminderRibbonCloseButton = styled.div`
20 | position: absolute;
21 | cursor: pointer;
22 | width: 18px;
23 | height: 18px;
24 | top: 12px;
25 | right: 16px;
26 | background-color: rgb(${colors.white});
27 | mask: url(${cross}) center no-repeat;
28 | mask-size: 90%;
29 | `;
30 |
31 | const StyledReminderRibbonContent = styled.div`
32 | position: relative;
33 | margin: 0 auto;
34 | max-width: ${({ maxWidth }) => `${maxWidth}px`};
35 | `;
36 |
37 | const StyledReminderRibbonMessage = styled.div`
38 | position: absolute;
39 | line-height: 40px;
40 | margin: 0 16px;
41 | `;
42 |
43 | class ReminderRibbon extends Component {
44 | state = {
45 | show: false,
46 | };
47 |
48 | componentDidMount() {
49 | getSupressReminderRibbon()
50 | .then(suppressReminder => {
51 | this.setState({ show: !suppressReminder });
52 | })
53 | .catch(error => {
54 | this.setState({ show: true });
55 | });
56 | }
57 |
58 | onClose = () => {
59 | this.setState({ show: false });
60 | saveSupressReminderRibbon(true);
61 | };
62 |
63 | bookmarkReminder = () => {
64 | if (typeof window.orientation !== 'undefined') {
65 | return 'Bookmark to protect against phishing attacks';
66 | } else {
67 | if (window.navigator.platform === 'MacIntel') {
68 | return '⌘+D to bookmark Balance Manager and protect yourself from phishing attacks';
69 | } else {
70 | return 'CTRL+D to bookmark Balance Manager and protect yourself from phishing attacks';
71 | }
72 | }
73 | };
74 |
75 | render = () => (
76 |
77 |
78 |
79 | {this.bookmarkReminder()}
80 |
81 |
82 |
83 |
84 | );
85 | }
86 |
87 | ReminderRibbon.propTypes = {
88 | maxWidth: PropTypes.number,
89 | };
90 |
91 | ReminderRibbon.defaultProps = {
92 | maxWidth: 600,
93 | };
94 |
95 | export default ReminderRibbon;
96 |
--------------------------------------------------------------------------------
/src/components/TransactionStatus.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import Spinner from './Spinner';
5 | import { lang } from 'balance-common';
6 | import circle from '../assets/circle.svg';
7 | import txSentIcon from '../assets/arrow-sent.svg';
8 | import txReceivedIcon from '../assets/arrow-received.svg';
9 | import txFailedIcon from '../assets/arrow-failed.svg';
10 | import { colors, fonts, responsive } from '../styles';
11 |
12 | const StyledTransactionStatus = styled.span`
13 | background: ${({ color }) =>
14 | color ? `rgba(${colors[color]}, 0.1)` : 'transparent'};
15 | border-radius: 8px;
16 | color: rgba(${colors.dark}, 0.6);
17 | font-weight: ${fonts.weight.semibold};
18 | padding: 4px 8px;
19 | position: relative;
20 |
21 | img {
22 | color: rgba(${colors.dark}, 0.6);
23 | margin-left: 4px;
24 | height: 10px;
25 | width: 10px;
26 | vertical-align: middle;
27 |
28 | @media screen and (max-width: 486px) {
29 | margin-left: 2px;
30 | }
31 |
32 | @media screen and (${responsive.xxs.max}) {
33 | display: none;
34 | }
35 | }
36 | `;
37 |
38 | const StyledSpinner = styled(Spinner)`
39 | position: absolute;
40 | right: -13px;
41 | `;
42 |
43 | const TransactionStatus = ({ tx, accountAddress, ...props }) => {
44 | let text = null;
45 | let color = null;
46 | let icon = null;
47 | let iconAlt = null;
48 |
49 | if (tx.pending) {
50 | text = lang.t('account.tx_pending');
51 | color = 'darkGrey';
52 | icon = null;
53 | iconAlt = null;
54 | } else if (tx.error) {
55 | text = lang.t('account.tx_failed');
56 | color = 'red';
57 | icon = txFailedIcon;
58 | iconAlt = `${lang.t('account.tx_failed')} icon`;
59 | } else {
60 | if (tx.from.toLowerCase() === tx.to.toLowerCase()) {
61 | text = lang.t('account.tx_self');
62 | color = 'blue';
63 | icon = circle;
64 | iconAlt = `${lang.t('account.tx_self')} icon`;
65 | } else if (tx.from.toLowerCase() === accountAddress.toLowerCase()) {
66 | text = lang.t('account.tx_sent');
67 | color = 'gold';
68 | icon = txSentIcon;
69 | iconAlt = `${lang.t('account.tx_sent')} icon`;
70 | } else {
71 | text = lang.t('account.tx_received');
72 | color = 'green';
73 | icon = txReceivedIcon;
74 | iconAlt = `${lang.t('account.tx_received')} icon`;
75 | }
76 | }
77 | return (
78 |
79 | {tx.pending && }
80 | {text}
81 | {icon &&
}
82 |
83 | );
84 | };
85 |
86 | TransactionStatus.propTypes = {
87 | tx: PropTypes.object.isRequired,
88 | accountAddress: PropTypes.string.isRequired,
89 | };
90 |
91 | export default TransactionStatus;
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "balance-manager",
3 | "description": "Ethereum Wallet Manager Interface for MetaMask, Ledger, Trezor and WalletConnect",
4 | "version": "0.8.4",
5 | "authors": [
6 | "Jin Chung ",
7 | "Pedro Gomes "
8 | ],
9 | "license": "GPL-3",
10 | "homepage": "https://manager.balance.io",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/balance-io/balance-manager.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/balance-io/balance-manager/issues"
17 | },
18 | "dependencies": {
19 | "@0x/subproviders": "^2.1.6",
20 | "@devshack/react-native-storage": "^0.2.2",
21 | "@ledgerhq/hw-app-eth": "^4.7.3",
22 | "@ledgerhq/hw-transport-u2f": "^4.7.3",
23 | "axios": "^0.18.0",
24 | "balance-common": "^0.6.1",
25 | "bignumber.js": "^7.0.1",
26 | "bowser": "^2.0.0-alpha.4",
27 | "ethereumjs-tx": "^1.3.4",
28 | "ethereumjs-wallet": "0.6.0",
29 | "hdkey": "^1.0.0",
30 | "i18next": "^11.3.2",
31 | "jsonp": "^0.2.1",
32 | "lodash": "^4.17.5",
33 | "piwik-react-router": "^0.12.1",
34 | "prop-types": "^15.6.1",
35 | "qrcode": "^1.2.0",
36 | "react": "^16.2.0",
37 | "react-dom": "^16.2.0",
38 | "react-qr-reader": "^2.1.0",
39 | "react-redux": "^5.0.7",
40 | "react-router-dom": "^4.2.2",
41 | "react-scripts": "^2.1.1",
42 | "redux": "^4.0.0",
43 | "redux-devtools-extension": "^2.13.2",
44 | "redux-thunk": "^2.2.0",
45 | "styled-components": "^3.2.3",
46 | "trezor-connect": "^5.0.13",
47 | "walletconnect": "^0.7.25",
48 | "web3": "^1.0.0-beta.34"
49 | },
50 | "scripts": {
51 | "remove-sourcemaps": "rm -rf build/static/js/*.map",
52 | "start": "cross-env HTTPS=true NODE_ENV=development react-scripts start",
53 | "build": "react-scripts build",
54 | "build:lambda": "netlify-lambda build src/lambda",
55 | "test": "react-scripts test --env=jsdom",
56 | "eject": "react-scripts eject",
57 | "precommit": "lint-staged",
58 | "format": "prettier --write \"src/**/*.js\"",
59 | "lint": "eslint src --ext .js"
60 | },
61 | "lint-staged": {
62 | "src/**/*.js": [
63 | "eslint src --ext .js",
64 | "prettier --write",
65 | "git add"
66 | ]
67 | },
68 | "devDependencies": {
69 | "cross-env": "^5.2.0",
70 | "eslint-config-prettier": "^2.9.0",
71 | "eslint-config-react-app": "^2.1.0",
72 | "eslint-plugin-flowtype": "^2.46.1",
73 | "eslint-plugin-import": "^2.9.0",
74 | "eslint-plugin-jsx-a11y": "^6.0.3",
75 | "eslint-plugin-prettier": "^2.6.0",
76 | "eslint-plugin-react": "^7.7.0",
77 | "husky": "^0.14.3",
78 | "lint-staged": "^7.1.0",
79 | "prettier": "^1.13.5"
80 | },
81 | "browserslist": [
82 | ">0.2%",
83 | "not dead",
84 | "not ie <= 11",
85 | "not op_mini all"
86 | ]
87 | }
88 |
--------------------------------------------------------------------------------
/src/pages/Metamask.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 | import { lang } from 'balance-common';
6 | import BaseLayout from '../layouts/base';
7 | import Account from '../views/Account';
8 | import Card from '../components/Card';
9 | import {
10 | metamaskUpdateMetamaskAccount,
11 | metamaskConnectInit,
12 | metamaskClearIntervals,
13 | } from '../reducers/_metamask';
14 | import { fonts, colors } from '../styles';
15 |
16 | const StyledWrapper = styled.div`
17 | width: 100%;
18 | `;
19 |
20 | const StyledMessage = styled.div`
21 | display: flex;
22 | align-items: center;
23 | justify-content: center;
24 | color: rgb(${colors.grey});
25 | font-weight: ${fonts.weight.medium};
26 | `;
27 |
28 | class Metamask extends Component {
29 | componentDidMount() {
30 | this.props.metamaskConnectInit();
31 | }
32 | renderMessage() {
33 | if (!this.props.web3Available) return lang.t('message.web3_not_available');
34 | if (!this.props.accountAddress) return lang.t('message.web3_not_unlocked');
35 | if (!this.props.network) return lang.t('message.web3_unknown_network');
36 | }
37 | componentWillUnmount() {
38 | this.props.metamaskClearIntervals();
39 | }
40 | render = () => (
41 |
42 |
43 | {this.props.fetching ||
44 | (this.props.network &&
45 | this.props.accountAddress &&
46 | this.props.web3Available) ? (
47 |
51 | ) : (
52 |
53 | {this.renderMessage()}
54 |
55 | )}
56 |
57 |
58 | );
59 | }
60 |
61 | Metamask.propTypes = {
62 | metamaskUpdateMetamaskAccount: PropTypes.func.isRequired,
63 | metamaskConnectInit: PropTypes.func.isRequired,
64 | metamaskClearIntervals: PropTypes.func.isRequired,
65 | web3Available: PropTypes.bool.isRequired,
66 | network: PropTypes.string.isRequired,
67 | fetching: PropTypes.bool.isRequired,
68 | match: PropTypes.object.isRequired,
69 | accountAddress: PropTypes.string,
70 | };
71 |
72 | Metamask.defaultProps = {
73 | accountAddress: null,
74 | };
75 |
76 | const reduxProps = ({ account, metamask }) => ({
77 | accountType: account.accountType,
78 | web3Available: metamask.web3Available,
79 | network: metamask.network,
80 | accountAddress: metamask.accountAddress,
81 | fetching: metamask.fetching,
82 | });
83 |
84 | export default connect(
85 | reduxProps,
86 | {
87 | metamaskUpdateMetamaskAccount,
88 | metamaskConnectInit,
89 | metamaskClearIntervals,
90 | },
91 | )(Metamask);
92 |
--------------------------------------------------------------------------------
/src/assets/chrome-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/globe.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
40 |
--------------------------------------------------------------------------------
/src/assets/brave-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reducers/_ledger.js:
--------------------------------------------------------------------------------
1 | import { lang } from 'balance-common';
2 | import {
3 | accountUpdateAccountAddress,
4 | accountUpdateNetwork,
5 | } from 'balance-common';
6 | import { ledgerEthInit, ledgerEthAccounts } from '../handlers/ledger-eth';
7 | import { notificationShow } from './_notification';
8 |
9 | // -- Constants ------------------------------------------------------------- //
10 | const LEDGER_CONNECT_REQUEST = 'ledger/LEDGER_CONNECT_REQUEST';
11 | const LEDGER_CONNECT_SUCCESS = 'ledger/LEDGER_CONNECT_SUCCESS';
12 | const LEDGER_CONNECT_FAILURE = 'ledger/LEDGER_CONNECT_FAILURE';
13 |
14 | const LEDGER_UPDATE_NETWORK = 'ledger/LEDGER_UPDATE_NETWORK';
15 | const LEDGER_CLEAR_STATE = 'ledger/LEDGER_CLEAR_STATE';
16 |
17 | // -- Actions --------------------------------------------------------------- //
18 |
19 | export const ledgerConnectInit = () => (dispatch, getState) => {
20 | const network = getState().ledger.network;
21 | dispatch({ type: LEDGER_CONNECT_REQUEST });
22 | ledgerEthInit(network)
23 | .then(() => {
24 | ledgerEthAccounts()
25 | .then(accounts => {
26 | if (accounts.length) {
27 | dispatch({ type: LEDGER_CONNECT_SUCCESS, payload: accounts });
28 | dispatch(
29 | accountUpdateAccountAddress(accounts[0].address, 'LEDGER'),
30 | );
31 | } else {
32 | dispatch(
33 | notificationShow(
34 | lang.t('notification.error.no_accounts_found'),
35 | true,
36 | ),
37 | );
38 | dispatch({ type: LEDGER_CONNECT_FAILURE });
39 | }
40 | })
41 | .catch(error => {
42 | dispatch({ type: LEDGER_CONNECT_FAILURE });
43 | });
44 | })
45 | .catch(error => {
46 | dispatch({ type: LEDGER_CONNECT_FAILURE });
47 | });
48 | };
49 |
50 | export const ledgerUpdateNetwork = network => (dispatch, getState) => {
51 | const accountAddress = getState().account.accountAddress;
52 | dispatch({ type: LEDGER_UPDATE_NETWORK, payload: network });
53 | dispatch(accountUpdateNetwork(network));
54 | dispatch(accountUpdateAccountAddress(accountAddress, 'LEDGER'));
55 | };
56 |
57 | export const ledgerClearState = () => dispatch => {
58 | dispatch({ type: LEDGER_CLEAR_STATE });
59 | };
60 |
61 | // -- Reducer --------------------------------------------------------------- //
62 | const INITIAL_STATE = {
63 | network: 'mainnet',
64 | fetching: false,
65 | accounts: [],
66 | };
67 |
68 | export default (state = INITIAL_STATE, action) => {
69 | switch (action.type) {
70 | case LEDGER_CONNECT_REQUEST:
71 | return {
72 | ...state,
73 | fetching: true,
74 | };
75 | case LEDGER_CONNECT_SUCCESS:
76 | return {
77 | ...state,
78 | fetching: false,
79 | accounts: action.payload,
80 | };
81 | case LEDGER_CONNECT_FAILURE:
82 | return {
83 | ...state,
84 | fetching: false,
85 | };
86 | case LEDGER_CLEAR_STATE:
87 | return {
88 | ...state,
89 | ...INITIAL_STATE,
90 | };
91 | default:
92 | return state;
93 | }
94 | };
95 |
--------------------------------------------------------------------------------
/src/components/ButtonCustom.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import Loader from './Loader';
5 | import { colors, fonts, shadows, transitions } from '../styles';
6 |
7 | const StyledIconImage = styled.div`
8 | position: absolute;
9 | height: 15px;
10 | width: 15px;
11 | margin: 0 8px;
12 | top: calc((100% - 15px) / 2);
13 | & img {
14 | width: 100%;
15 | height: 100%;
16 | }
17 | `;
18 |
19 | const StyledButton = styled.button`
20 | transition: ${transitions.base};
21 | position: relative;
22 | border: none;
23 | border-style: none;
24 | box-sizing: border-box;
25 | background-color: ${({ bgColor }) => `rgb(${colors[bgColor]})`};
26 | color: ${({ txtColor }) => `rgb(${colors[txtColor]})`};
27 | box-shadow: ${shadows.soft};
28 | border-radius: 7px;
29 | font-size: 0.75em;
30 | font-weight: ${fonts.weight.semibold};
31 | padding: ${({ img, left }) =>
32 | img ? (left ? '8px 12px 8px 28px' : '8px 28px 8px 12px') : '8px 12px'};
33 | margin: 5px;
34 | height: 27px;
35 | cursor: ${({ disabled }) => (disabled ? 'auto' : 'pointer')};
36 | will-change: transform;
37 |
38 | &:disabled {
39 | opacity: 0.6;
40 | box-shadow: ${({ outline }) =>
41 | outline ? 'none' : `${shadows.soft}`} !important;
42 | }
43 |
44 | &:active,
45 | &:focus {
46 | opacity: 1;
47 | box-shadow: ${({ outline }) =>
48 | outline ? 'none' : `${shadows.soft}`} !important;
49 | }
50 |
51 | & ${StyledIconImage} {
52 | right: ${({ left }) => (left ? 'auto' : '0')};
53 | left: ${({ left }) => (left ? '0' : 'auto')};
54 | display: ${({ img }) => (img ? 'block' : 'none')};
55 | }
56 |
57 | @media (hover: hover) {
58 | &:hover {
59 | opacity: 0.6;
60 | box-shadow: ${({ outline }) =>
61 | outline ? 'none' : `${shadows.soft}`} !important;
62 | }
63 | }
64 | `;
65 |
66 | const Button = ({
67 | children,
68 | fetching,
69 | outline,
70 | txtColor,
71 | bgColor,
72 | type,
73 | disabled,
74 | img,
75 | left,
76 | round,
77 | ...props
78 | }) => (
79 |
88 |
89 |
90 |
91 | {fetching ? (
92 |
93 | ) : (
94 | children
95 | )}
96 |
97 | );
98 |
99 | Button.propTypes = {
100 | children: PropTypes.node.isRequired,
101 | img: PropTypes.string.isRequired,
102 | fetching: PropTypes.bool,
103 | type: PropTypes.string,
104 | txtColor: PropTypes.string,
105 | bgColor: PropTypes.string,
106 | disabled: PropTypes.bool,
107 | left: PropTypes.bool,
108 | };
109 |
110 | Button.defaultProps = {
111 | fetching: false,
112 | type: 'button',
113 | txtColor: 'dark',
114 | bgColor: 'white',
115 | disabled: false,
116 | left: false,
117 | };
118 |
119 | export default Button;
120 |
--------------------------------------------------------------------------------
/src/reducers/_warning.js:
--------------------------------------------------------------------------------
1 | import { lang } from 'balance-common';
2 |
3 | // -- Constants ------------------------------------------------------------- //
4 | const WARNING_PARSE = 'warning/WARNING_PARSE';
5 | const WARNING_SHOW = 'warning/WARNING_SHOW';
6 | const WARNING_HIDE = 'warning/WARNING_HIDE';
7 | const WARNING_CLEAR = 'warning/WARNING_CLEAR';
8 |
9 | const WARNING_USER_IS_OFFLINE = 'warning/WARNING_USER_IS_OFFLINE';
10 | const WARNING_USER_IS_ONLINE = 'warning/WARNING_USER_IS_ONLINE';
11 |
12 | // -- Actions --------------------------------------------------------------- //
13 |
14 | export const warningShow = warning => (dispatch, getState) => {
15 | dispatch({ type: WARNING_PARSE });
16 | const warnings = getState().warning.active;
17 | const isActive = warnings.filter(
18 | activeWarning => activeWarning.key === warning.key,
19 | ).length;
20 | if (!isActive) {
21 | warnings.push(warning);
22 | }
23 | dispatch({ type: WARNING_SHOW, payload: warnings });
24 | };
25 |
26 | export const warningHide = key => (dispatch, getState) => {
27 | dispatch({ type: WARNING_PARSE });
28 | const warnings = getState().warning.active;
29 | const newWarnings = warnings.filter(warning => warning.key !== key);
30 | dispatch({ type: WARNING_SHOW, payload: newWarnings });
31 | };
32 |
33 | export const warningOffline = () => dispatch => {
34 | dispatch({ type: WARNING_USER_IS_OFFLINE });
35 | dispatch(
36 | warningShow({
37 | key: 'USER_IS_OFFLINE',
38 | color: 'red',
39 | message: lang.t('warning.user_is_offline'),
40 | action: () => {},
41 | }),
42 | );
43 | };
44 |
45 | let timeoutHide;
46 |
47 | export const warningOnline = () => dispatch => {
48 | dispatch({ type: WARNING_USER_IS_ONLINE });
49 | clearTimeout(timeoutHide);
50 | dispatch(warningHide('USER_IS_OFFLINE'));
51 | dispatch(
52 | warningShow({
53 | key: 'USER_IS_ONLINE',
54 | color: 'green',
55 | message: lang.t('warning.user_is_online'),
56 | action: () => {},
57 | }),
58 | );
59 | timeoutHide = setTimeout(() => dispatch(warningHide('USER_IS_ONLINE')), 3000);
60 | };
61 |
62 | export const warningClear = () => dispatch => dispatch({ type: WARNING_CLEAR });
63 |
64 | // -- Reducer --------------------------------------------------------------- //
65 | const INITIAL_STATE = {
66 | online: true,
67 | show: true,
68 | active: [],
69 | };
70 |
71 | export default (state = INITIAL_STATE, action) => {
72 | switch (action.type) {
73 | case WARNING_USER_IS_OFFLINE:
74 | return {
75 | ...state,
76 | online: false,
77 | };
78 | case WARNING_USER_IS_ONLINE:
79 | return {
80 | ...state,
81 | online: true,
82 | };
83 | case WARNING_PARSE:
84 | return {
85 | ...state,
86 | show: false,
87 | };
88 | case WARNING_SHOW:
89 | case WARNING_HIDE:
90 | return {
91 | ...state,
92 | show: true,
93 | active: action.payload,
94 | };
95 | case WARNING_CLEAR:
96 | return { ...state, show: false, active: [] };
97 | default:
98 | return state;
99 | }
100 | };
101 |
--------------------------------------------------------------------------------
/src/modals/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 |
6 | import Column from '../components/Column';
7 |
8 | import ExchangeModal from './ExchangeModal';
9 | import SendModal from './SendModal';
10 | import ReceiveModal from './ReceiveModal';
11 | import DonateModal from './DonateModal';
12 | import WalletConnectModal from './WalletConnectModal';
13 | import { modalClose } from '../reducers/_modal';
14 | import { sendClearFields } from 'balance-common';
15 | import { exchangeClearFields } from '../reducers/_exchange';
16 |
17 | import { colors, transitions } from '../styles';
18 |
19 | const StyledLightbox = styled.div`
20 | position: absolute;
21 | top: 0;
22 | left: 0;
23 | right: 0;
24 | bottom: 0;
25 | z-index: 2;
26 | transition: ${transitions.base};
27 | opacity: ${({ modal }) => (modal ? 1 : 0)};
28 | visibility: ${({ modal }) => (modal ? 'visible' : 'hidden')};
29 | pointer-events: ${({ modal }) => (modal ? 'auto' : 'none')};
30 | background: rgba(${colors.dark}, 0.2);
31 | `;
32 |
33 | const StyledHitbox = styled.div`
34 | position: absolute;
35 | top: 0;
36 | left: 0;
37 | right: 0;
38 | bottom: 0;
39 | `;
40 |
41 | const StyledContainer = styled.div`
42 | position: relative;
43 | width: 100%;
44 | height: 100%;
45 | padding: 15px;
46 | display: flex;
47 | align-items: center;
48 | justify-content: center;
49 | `;
50 |
51 | const reduxProps = ({ modal }) => ({
52 | modal: modal.modal,
53 | });
54 |
55 | class Modal extends Component {
56 | static propTypes = {
57 | modalClose: PropTypes.func.isRequired,
58 | sendClearFields: PropTypes.func.isRequired,
59 | exchangeClearFields: PropTypes.func.isRequired,
60 | modal: PropTypes.string.isRequired,
61 | };
62 |
63 | modalController = () => {
64 | switch (this.props.modal) {
65 | case 'EXCHANGE_MODAL':
66 | return ;
67 | case 'SEND_MODAL':
68 | return ;
69 | case 'DONATION_MODAL':
70 | return ;
71 | case 'RECEIVE_MODAL':
72 | return ;
73 | case 'WALLET_CONNECT':
74 | return ;
75 | default:
76 | return ;
77 | }
78 | };
79 |
80 | onClose = () => {
81 | this.props.sendClearFields();
82 | this.props.exchangeClearFields();
83 | this.props.modalClose();
84 | };
85 |
86 | render = () => {
87 | const body = document.body || document.getElementsByTagName('body')[0];
88 |
89 | if (this.props.modal) {
90 | body.style.overflow = 'hidden';
91 | } else {
92 | body.style.overflow = 'auto';
93 | }
94 |
95 | return (
96 |
97 |
98 |
99 | {this.modalController()}
100 |
101 |
102 | );
103 | };
104 | }
105 |
106 | export default connect(
107 | reduxProps,
108 | {
109 | modalClose,
110 | sendClearFields,
111 | exchangeClearFields,
112 | },
113 | )(Modal);
114 |
--------------------------------------------------------------------------------
/src/components/Card.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import Loader from '../components/Loader';
5 | import { colors, fonts, shadows, transitions } from '../styles';
6 |
7 | const StyledCard = styled.div`
8 | transition: ${transitions.base};
9 | position: relative;
10 | width: 100%;
11 | max-width: ${({ maxWidth }) => (maxWidth ? `${maxWidth}px` : 'none')};
12 | border: none;
13 | border-style: none;
14 | color: rgb(${colors.dark});
15 | background-color: ${({ background }) => `rgb(${colors[background]})`};
16 | box-shadow: ${shadows.soft};
17 | border-radius: 10px;
18 | font-size: ${fonts.size.medium};
19 | font-weight: ${fonts.weight.normal};
20 | margin: 0 auto;
21 | text-align: left;
22 | overflow: ${({ allowOverflow }) => (allowOverflow ? 'visible' : 'hidden')};
23 | `;
24 |
25 | const StyledContent = styled.div`
26 | min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : '0')};
27 | padding: ${({ padding }) => (padding ? padding : null)};
28 | transition: ${transitions.base};
29 | opacity: ${({ fetching }) => (fetching ? 0 : 1)};
30 | visibility: ${({ fetching }) => (fetching ? 'hidden' : 'visible')};
31 | pointer-events: ${({ fetching }) => (fetching ? 'none' : 'auto')};
32 | display: flex;
33 | justify-content: center;
34 | align-items: center;
35 | width: 100%;
36 | `;
37 |
38 | const StyledFetching = styled.div`
39 | position: absolute;
40 | top: 0;
41 | bottom: 0;
42 | left: 0;
43 | right: 0;
44 | display: flex;
45 | flex-direction: column;
46 | align-items: center;
47 | justify-content: center;
48 | transition: ${transitions.short};
49 | opacity: ${({ fetching }) => (fetching ? 1 : 0)};
50 | visibility: ${({ fetching }) => (fetching ? 'visible' : 'hidden')};
51 | pointer-events: ${({ fetching }) => (fetching ? 'auto' : 'none')};
52 | `;
53 |
54 | const StyledMessage = styled.div`
55 | display: flex;
56 | align-items: center;
57 | justify-content: center;
58 | color: rgb(${colors.grey});
59 | font-weight: ${fonts.weight.medium};
60 | margin: 20px;
61 | `;
62 |
63 | const Card = ({
64 | fetching,
65 | fetchingMessage,
66 | allowOverflow,
67 | background,
68 | maxWidth,
69 | minHeight,
70 | padding,
71 | children,
72 | ...props
73 | }) => (
74 |
80 |
81 | {fetchingMessage}
82 |
83 |
84 |
85 | {children}
86 |
87 |
88 | );
89 |
90 | Card.propTypes = {
91 | children: PropTypes.node.isRequired,
92 | fetching: PropTypes.bool,
93 | allowOverflow: PropTypes.bool,
94 | fetchingMessage: PropTypes.string,
95 | background: PropTypes.string,
96 | maxWidth: PropTypes.number,
97 | };
98 |
99 | Card.defaultProps = {
100 | fetching: false,
101 | allowOverflow: false,
102 | fetchingMessage: '',
103 | background: 'white',
104 | maxWidth: null,
105 | minHeight: null,
106 | };
107 |
108 | export default Card;
109 |
--------------------------------------------------------------------------------
/src/components/CopyToClipboard/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | import { fonts, colors, transitions, responsive } from '../../styles';
4 |
5 | const StyledInputTextSize = `
6 | font-size: ${fonts.size.medium};
7 | max-width: 400px;
8 | `;
9 |
10 | const StyledInputTextSizeSMedium = `
11 | font-size: ${fonts.size.smedium};
12 | max-width: 370px;
13 | `;
14 |
15 | const StyledInputTextSizeSmall = `
16 | font-size: ${fonts.size.small};
17 | max-width: 320px;
18 | `;
19 |
20 | const StyledInputTextSizeXSmall = `
21 | font-size: ${fonts.size.xsmall};
22 | max-width: 294px;
23 | `;
24 |
25 | const StyledIcon = styled.img`
26 | cursor: pointer;
27 | transition: ${transitions.base};
28 | width: 16px;
29 | height: 16px;
30 | opacity: 0;
31 | vertical-align: middle;
32 | display: ${({ displayIcon }) => (displayIcon ? 'inline' : 'none')};
33 |
34 | @media screen and (${responsive.xxs.max}) {
35 | display: none;
36 | }
37 | `;
38 |
39 | const StyledText = styled.p`
40 | transition: ${transitions.base};
41 | font-weight: ${fonts.weight.medium};
42 | font-size: ${fonts.size.small};
43 | opacity: 0;
44 | position: absolute;
45 | top: 200%;
46 | right: calc(50% - 91px);
47 | font-family: ${fonts.family.SFMono};
48 | letter-spacing: -0.2px;
49 | `;
50 |
51 | const StyledCopyToClipboard = styled.div`
52 | @media (hover: hover) {
53 | &:hover ${StyledIcon} {
54 | opacity: ${({ iconOnHover }) => (iconOnHover ? '1' : '0')};
55 | }
56 | &:hover ${StyledText} {
57 | opacity: ${({ iconOnHover }) => (iconOnHover ? '0' : '0.7')};
58 | }
59 | }
60 | `;
61 |
62 | const StyledInputText = styled.input`
63 | background-color: transparent;
64 | color: transparent;
65 | text-shadow: 0 0 0 rgb(${colors.mediumGrey});
66 | font-style: normal;
67 | font-stretch: normal;
68 | line-height: normal;
69 | letter-spacing: normal;
70 | text-align: left;
71 | font-weight: ${fonts.weight.medium};
72 | font-family: ${fonts.family.SFMono};
73 | line-height: 1.25;
74 | width: 100%;
75 | ${StyledInputTextSize};
76 |
77 | ${({ isTopAddress }) =>
78 | isTopAddress &&
79 | css`
80 | @media screen and (max-width: 810px) {
81 | ${StyledInputTextSizeSMedium};
82 | }
83 |
84 | @media screen and (max-width: 780px) {
85 | ${StyledInputTextSizeSmall};
86 | }
87 |
88 | @media screen and (max-width: 730px) {
89 | ${StyledInputTextSizeXSmall};
90 | }
91 |
92 | @media screen and (max-width: 712px) {
93 | ${StyledInputTextSize};
94 | }
95 | `};
96 |
97 | @media screen and (${responsive.xs.max}) {
98 | ${StyledInputTextSizeSMedium};
99 | }
100 |
101 | @media screen and (max-width: 450px) {
102 | ${StyledInputTextSizeSmall};
103 | }
104 |
105 | @media screen and (max-width: 360px) {
106 | ${StyledInputTextSizeXSmall};
107 | }
108 |
109 | @media screen and (max-width: 336px) {
110 | font-size: ${fonts.size.tiny};
111 | }
112 | `;
113 |
114 | const StyledInput = styled(StyledInputText)`
115 | -webkit-appearance: none;
116 | border: none;
117 | cursor: pointer;
118 | display: inline-block;
119 | outline: none;
120 | vertical-align: middle;
121 | `;
122 |
123 | export { StyledCopyToClipboard, StyledInput, StyledText, StyledIcon };
124 |
--------------------------------------------------------------------------------
/src/reducers/_trezor.js:
--------------------------------------------------------------------------------
1 | import { lang } from 'balance-common';
2 | import {
3 | accountUpdateAccountAddress,
4 | accountUpdateNetwork,
5 | } from 'balance-common';
6 | import { trezorEthInit, trezorEthAccounts } from '../handlers/trezor-eth';
7 | import { notificationShow } from './_notification';
8 |
9 | // -- Constants ------------------------------------------------------------- //
10 | const TREZOR_CONNECT_REQUEST = 'trezor/TREZOR_CONNECT_REQUEST';
11 | const TREZOR_CONNECT_SUCCESS = 'trezor/TREZOR_CONNECT_SUCCESS';
12 | const TREZOR_CONNECT_FAILURE = 'trezor/TREZOR_CONNECT_FAILURE';
13 |
14 | const TREZOR_UPDATE_NETWORK = 'trezor/TREZOR_UPDATE_NETWORK';
15 | const TREZOR_CLEAR_STATE = 'trezor/TREZOR_CLEAR_STATE';
16 |
17 | // -- Actions --------------------------------------------------------------- //
18 |
19 | export const trezorConnectInit = () => (dispatch, getState) => {
20 | const network = getState().account.network;
21 | dispatch({ type: TREZOR_CONNECT_REQUEST });
22 | trezorEthInit(network)
23 | .then(() => {
24 | trezorEthAccounts()
25 | .then(accounts => {
26 | if (accounts.length) {
27 | dispatch({ type: TREZOR_CONNECT_SUCCESS, payload: accounts });
28 | dispatch(
29 | accountUpdateAccountAddress(accounts[0].address, 'TREZOR'),
30 | );
31 | } else {
32 | dispatch(
33 | notificationShow(
34 | lang.t('notification.error.no_accounts_found'),
35 | true,
36 | ),
37 | );
38 | dispatch({ type: TREZOR_CONNECT_FAILURE });
39 | }
40 | })
41 | .catch(error => {
42 | console.log(error);
43 | dispatch({ type: TREZOR_CONNECT_FAILURE });
44 | });
45 | })
46 | .catch(error => {
47 | console.log(error);
48 | dispatch({ type: TREZOR_CONNECT_FAILURE });
49 | });
50 | };
51 |
52 | export const trezorUpdateNetwork = network => (dispatch, getState) => {
53 | const accountAddress = getState().account.accountAddress;
54 | dispatch({ type: TREZOR_UPDATE_NETWORK, payload: network });
55 | dispatch(accountUpdateNetwork(network));
56 | dispatch(accountUpdateAccountAddress(accountAddress, 'TREZOR'));
57 | dispatch(trezorConnectInit());
58 | };
59 |
60 | export const trezorClearState = () => dispatch => {
61 | dispatch({ type: TREZOR_CLEAR_STATE });
62 | };
63 |
64 | // -- Reducer --------------------------------------------------------------- //
65 | const INITIAL_STATE = {
66 | network: 'mainnet',
67 | fetching: false,
68 | accounts: [],
69 | };
70 |
71 | export default (state = INITIAL_STATE, action) => {
72 | switch (action.type) {
73 | case TREZOR_CONNECT_REQUEST:
74 | return {
75 | ...state,
76 | fetching: true,
77 | };
78 | case TREZOR_CONNECT_SUCCESS:
79 | return {
80 | ...state,
81 | fetching: false,
82 | accounts: action.payload,
83 | };
84 | case TREZOR_CONNECT_FAILURE:
85 | return {
86 | ...state,
87 | fetching: false,
88 | };
89 | case TREZOR_UPDATE_NETWORK:
90 | return {
91 | ...state,
92 | fetching: false,
93 | };
94 | case TREZOR_CLEAR_STATE:
95 | return {
96 | ...state,
97 | ...INITIAL_STATE,
98 | };
99 | default:
100 | return state;
101 | }
102 | };
103 |
--------------------------------------------------------------------------------
/src/pages/Ledger.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 | import { lang } from 'balance-common';
6 | import HelpSvg from '../assets/help.svg';
7 | import BaseLayout from '../layouts/base';
8 | import Card from '../components/Card';
9 | import Button from '../components/Button';
10 | import Account from '../views/Account';
11 | import { ledgerConnectInit } from '../reducers/_ledger';
12 | import { fonts, colors } from '../styles';
13 |
14 | const FailedConnectionMessage = styled.div`
15 | align-items: center;
16 | color: rgb(${colors.grey});
17 | display: flex;
18 | font-weight: ${fonts.weight.medium};
19 | justify-content: center;
20 | margin: 20px 20px 30px 20px;
21 | `;
22 |
23 | const StyledCardContainer = styled.div`
24 | align-items: center;
25 | display: flex;
26 | flex-direction: column;
27 | justify-content: center;
28 | width: 100%;
29 | `;
30 |
31 | const StyledWrapper = styled.div`
32 | width: 100%;
33 | `;
34 |
35 | const HelpFooter = styled.a`
36 | align-items: center;
37 | background-color: rgb(${colors.blue});
38 | border-radius: 10px;
39 | cursor: pointer;
40 | display: flex;
41 | justify-content: center;
42 | margin-top: 20px;
43 | padding: 15px;
44 | width: 100%;
45 |
46 | &:hover {
47 | background-color: rgb(${colors.blueHover});
48 | & > img {
49 | opacity: 1;
50 | }
51 | }
52 | &:active {
53 | background-color: rgb(${colors.blueActive});
54 | }
55 | `;
56 |
57 | const HelpIcon = styled.img`
58 | color: rgb(${colors.white});
59 | height: ${fonts.size.smedium};
60 | margin-left: 7px;
61 | opacity: 0.75;
62 | width: ${fonts.size.smedium};
63 | `;
64 |
65 | class Ledger extends Component {
66 | componentDidMount() {
67 | this.connectLedger();
68 | }
69 | connectLedger = () => this.props.ledgerConnectInit();
70 | render() {
71 | const { accounts, fetching, match } = this.props;
72 |
73 | return (
74 |
75 |
76 | {fetching || accounts.length ? (
77 |
84 | ) : (
85 |
86 |
87 |
88 | {lang.t('message.failed_ledger_connection')}
89 |
90 |
93 |
94 |
95 | )}
96 | {(fetching || !accounts.length) && (
97 |
101 | Need help connecting your Ledger?
102 |
103 |
104 | )}
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | Ledger.propTypes = {
112 | ledgerConnectInit: PropTypes.func.isRequired,
113 | fetching: PropTypes.bool.isRequired,
114 | match: PropTypes.object.isRequired,
115 | accounts: PropTypes.array.isRequired,
116 | };
117 |
118 | const reduxProps = ({ account, ledger }) => ({
119 | accountType: account.accountType,
120 | accounts: ledger.accounts,
121 | fetching: ledger.fetching,
122 | });
123 |
124 | export default connect(
125 | reduxProps,
126 | {
127 | ledgerConnectInit,
128 | },
129 | )(Ledger);
130 |
--------------------------------------------------------------------------------
/src/pages/Trezor.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import styled from 'styled-components';
5 | import { lang } from 'balance-common';
6 | import HelpSvg from '../assets/help.svg';
7 | import BaseLayout from '../layouts/base';
8 | import Card from '../components/Card';
9 | import Button from '../components/Button';
10 | import Account from '../views/Account';
11 | import { trezorConnectInit } from '../reducers/_trezor';
12 | import { fonts, colors } from '../styles';
13 |
14 | const FailedConnectionMessage = styled.div`
15 | align-items: center;
16 | color: rgb(${colors.grey});
17 | display: flex;
18 | font-weight: ${fonts.weight.medium};
19 | justify-content: center;
20 | margin: 20px 20px 30px 20px;
21 | `;
22 |
23 | const StyledCardContainer = styled.div`
24 | align-items: center;
25 | display: flex;
26 | flex-direction: column;
27 | justify-content: center;
28 | width: 100%;
29 | `;
30 |
31 | const StyledWrapper = styled.div`
32 | width: 100%;
33 | `;
34 |
35 | const HelpFooter = styled.a`
36 | align-items: center;
37 | background-color: rgb(${colors.blue});
38 | border-radius: 10px;
39 | cursor: pointer;
40 | display: flex;
41 | justify-content: center;
42 | margin-top: 20px;
43 | padding: 15px;
44 | width: 100%;
45 |
46 | &:hover {
47 | background-color: rgb(${colors.blueHover});
48 | & > img {
49 | opacity: 1;
50 | }
51 | }
52 | &:active {
53 | background-color: rgb(${colors.blueActive});
54 | }
55 | `;
56 |
57 | const HelpIcon = styled.img`
58 | color: rgb(${colors.white});
59 | height: ${fonts.size.smedium};
60 | margin-left: 7px;
61 | opacity: 0.75;
62 | width: ${fonts.size.smedium};
63 | `;
64 |
65 | class Trezor extends Component {
66 | componentDidMount() {
67 | this.connectTrezor();
68 | }
69 | connectTrezor = () => this.props.trezorConnectInit();
70 | render() {
71 | const { accounts, fetching, match } = this.props;
72 |
73 | return (
74 |
75 |
76 | {fetching || accounts.length ? (
77 |
84 | ) : (
85 |
86 |
87 |
88 | {lang.t('message.failed_trezor_connection')}
89 |
90 |
93 |
94 |
95 | )}
96 | {(fetching || !accounts.length) && (
97 |
101 | Need help connecting your Trezor?
102 |
103 |
104 | )}
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | Trezor.propTypes = {
112 | trezorConnectInit: PropTypes.func.isRequired,
113 | fetching: PropTypes.bool.isRequired,
114 | match: PropTypes.object.isRequired,
115 | accounts: PropTypes.array.isRequired,
116 | };
117 |
118 | const reduxProps = ({ account, trezor }) => ({
119 | accountType: account.accountType,
120 | accounts: trezor.accounts,
121 | fetching: trezor.fetching,
122 | });
123 |
124 | export default connect(
125 | reduxProps,
126 | {
127 | trezorConnectInit,
128 | },
129 | )(Trezor);
130 |
--------------------------------------------------------------------------------
/src/handlers/web3.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3';
2 | import { ledgerEthSignTransaction } from './ledger-eth';
3 | import piwik from '../piwik';
4 | import { trezorEthSignTransaction } from './trezor-eth';
5 | import { walletConnectSignTransaction } from './walletconnect';
6 |
7 | /**
8 | * @desc web3 http instance
9 | */
10 | export const web3Instance = new Web3(
11 | new Web3.providers.HttpProvider(`https://mainnet.infura.io/`),
12 | );
13 |
14 | /**
15 | * @desc send signed transaction
16 | * @param {String} signedTx
17 | * @return {Promise}
18 | */
19 | export const web3SendSignedTransaction = signedTx =>
20 | new Promise((resolve, reject) => {
21 | const serializedTx = typeof signedTx === 'string' ? signedTx : signedTx.raw;
22 | web3Instance.eth
23 | .sendSignedTransaction(serializedTx)
24 | .once('transactionHash', txHash => resolve(txHash))
25 | .catch(error => reject(error));
26 | });
27 |
28 | /**
29 | * @desc metamask send transaction
30 | * @param {Object} transaction { from, to, value, data, gasPrice}
31 | * @return {Promise}
32 | */
33 | export const web3MetamaskSendTransaction = txDetails =>
34 | new Promise((resolve, reject) => {
35 | if (typeof window.web3 !== 'undefined') {
36 | window.web3.eth.sendTransaction(txDetails, (err, txHash) => {
37 | if (err) {
38 | reject(err);
39 | }
40 | resolve(txHash);
41 | });
42 | } else {
43 | throw new Error(`Metamask is not present`);
44 | }
45 | });
46 |
47 | /**
48 | * @desc walletconnect send transaction
49 | * @param {Object} transaction { from, to, value, data, gasPrice}
50 | * @return {Promise}
51 | */
52 | export const web3WalletConnectSendTransaction = txDetails =>
53 | new Promise((resolve, reject) => {
54 | walletConnectSignTransaction(txDetails)
55 | .then(txHash => {
56 | resolve(txHash);
57 | })
58 | .catch(error => {
59 | reject(error);
60 | });
61 | });
62 |
63 | /**
64 | * @desc ledger send transaction
65 | * @param {Object} transaction { from, to, value, data, gasPrice}
66 | * @return {Promise}
67 | */
68 | export const web3LedgerSendTransaction = txDetails =>
69 | new Promise((resolve, reject) => {
70 | ledgerEthSignTransaction(txDetails)
71 | .then(signedTx =>
72 | web3SendSignedTransaction(signedTx)
73 | .then(txHash => resolve(txHash))
74 | .catch(error => reject(error)),
75 | )
76 | .catch(error => reject(error));
77 | });
78 |
79 | export const web3TrezorSendTransaction = txDetails =>
80 | new Promise((resolve, reject) => {
81 | trezorEthSignTransaction(txDetails)
82 | .then(signedTx =>
83 | web3SendSignedTransaction(signedTx)
84 | .then(txHash => resolve(txHash))
85 | .catch(error => reject(error)),
86 | )
87 | .catch(error => reject(error));
88 | });
89 |
90 | /**
91 | * @desc send transaction controller given asset transfered and account type
92 | * @param {Object} transaction { asset, from, to, amount, gasPrice }
93 | * @return {Promise}
94 | */
95 | export const web3SendTransactionMultiWallet = ({
96 | accountType,
97 | tracking,
98 | transaction,
99 | }) => {
100 | piwik.push([
101 | 'trackEvent',
102 | 'Send',
103 | accountType,
104 | tracking.name,
105 | tracking.amount,
106 | ]);
107 | let method = null;
108 | switch (accountType) {
109 | case 'METAMASK':
110 | method = web3MetamaskSendTransaction;
111 | break;
112 | case 'LEDGER':
113 | method = web3LedgerSendTransaction;
114 | break;
115 | case 'TREZOR':
116 | method = web3TrezorSendTransaction;
117 | break;
118 | case 'WALLETCONNECT':
119 | method = web3WalletConnectSendTransaction;
120 | break;
121 | default:
122 | method = web3MetamaskSendTransaction;
123 | break;
124 | }
125 | return method(transaction);
126 | };
127 |
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
65 |
--------------------------------------------------------------------------------
/src/components/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { keyframes } from 'styled-components';
4 | import { lang } from 'balance-common';
5 | import { colors, fonts, shadows, responsive } from '../styles';
6 |
7 | const shimmer = keyframes`
8 | 0% {
9 | background-position: -468px 0
10 | }
11 |
12 | 100% {
13 | background-position: 468px 0
14 | }
15 |
16 | `;
17 |
18 | const StyledInputWrapper = styled.div`
19 | display: flex;
20 | flex-direction: column;
21 | justify-content: flex-end;
22 | width: 100%;
23 | opacity: ${({ fetching, disabled }) => (disabled && !fetching ? '0.5' : '1')};
24 | `;
25 |
26 | const StyledLabel = styled.label`
27 | color: rgb(${colors.grey});
28 | font-size: 13px;
29 | font-weight: ${fonts.weight.semibold};
30 | width: 100%;
31 |
32 | & ~ input {
33 | margin-top: 8px;
34 | }
35 | `;
36 |
37 | const StyledInput = styled.input`
38 | width: 100%;
39 | background: rgb(${colors.white});
40 | padding: 12px;
41 | border: none;
42 | border-style: none;
43 | font-family: ${({ monospace }) =>
44 | monospace ? `${fonts.family.SFMono}` : `inherit`};
45 | font-size: ${fonts.size.h6};
46 | font-weight: ${fonts.weight.semibold};
47 | font-style: normal;
48 | font-stretch: normal;
49 | line-height: normal;
50 | letter-spacing: normal;
51 | text-align: left;
52 | border-radius: 8px;
53 | -webkit-box-shadow: ${shadows.medium};
54 | box-shadow: ${shadows.medium};
55 | outline: none;
56 | ${({ fetching }) =>
57 | fetching &&
58 | `
59 | color: rgba(${colors.dark}, 0.7);
60 | -webkit-animation-duration: 1s;
61 | -webkit-animation-fill-mode: forwards;
62 | -webkit-animation-iteration-count: infinite;
63 | -webkit-animation-name: ${shimmer};
64 | -webkit-animation-timing-function: linear;
65 | background: #f6f7f8;
66 | background-image: -webkit-gradient(linear, left center, right center, from(#f6f7f8), color-stop(.2, #edeef1), color-stop(.4, #f6f7f8), to(#f6f7f8));
67 | background-image: -webkit-linear-gradient(left, #f6f7f8 0%, #edeef1 20%, #f6f7f8 40%, #f6f7f8 100%);
68 | background-repeat: no-repeat;
69 | background-size: 800px 104px;
70 | `};
71 |
72 | &::placeholder {
73 | color: rgba(${colors.grey}, 0.8);
74 | font-weight: ${fonts.weight.medium};
75 | opacity: 1;
76 | }
77 |
78 | @media screen and (${responsive.sm.max}) {
79 | padding: 8px 10px;
80 | }
81 | `;
82 |
83 | const Input = ({
84 | children,
85 | fetching,
86 | label,
87 | type,
88 | disabled,
89 | value,
90 | placeholder,
91 | monospace,
92 | ...props
93 | }) => {
94 | let _label = label;
95 | let _placeholder = placeholder;
96 | if (!label) {
97 | if (type === 'email') {
98 | _label = lang.t('input.email');
99 | _placeholder = lang.t('input.email_placeholder');
100 | } else if (type === 'password') {
101 | _label = lang.t('input.password');
102 | _placeholder = lang.t('input.password_placeholder');
103 | } else if (type === 'text') {
104 | _label = lang.t('input.input_text');
105 | }
106 | }
107 | if (!placeholder) {
108 | if (type === 'email') {
109 | _placeholder = lang.t('input.email_placeholder');
110 | } else if (type === 'password') {
111 | _placeholder = lang.t('input.password_placeholder');
112 | } else if (type === 'text') {
113 | _placeholder = lang.t('input.input_placeholder');
114 | }
115 | }
116 | return (
117 |
118 | {_label !== 'Input' && {_label}}
119 |
128 | {children}
129 |
130 | );
131 | };
132 |
133 | Input.propTypes = {
134 | type: PropTypes.string.isRequired,
135 | label: PropTypes.string,
136 | placeholder: PropTypes.string,
137 | fetching: PropTypes.bool,
138 | monospace: PropTypes.bool,
139 | disabled: PropTypes.bool,
140 | };
141 |
142 | Input.defaultProps = {
143 | label: '',
144 | placeholder: '',
145 | fetching: false,
146 | monospace: false,
147 | disabled: false,
148 | };
149 |
150 | export default Input;
151 |
--------------------------------------------------------------------------------
/src/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { css } from 'styled-components';
4 | import Loader from './Loader';
5 | import { colors, fonts, shadows, transitions } from '../styles';
6 |
7 | const StyledIcon = styled.span`
8 | height: 16px;
9 | margin-right: 6px;
10 | width: 16px;
11 | `;
12 |
13 | const StyledButton = styled.button`
14 | transition: ${transitions.button};
15 | border: none;
16 | border-style: none;
17 | box-sizing: border-box;
18 | background-color: ${({ outline, color }) =>
19 | outline ? 'transparent' : `rgb(${colors[color]})`};
20 | border: ${({ outline, color }) =>
21 | outline ? `1px solid rgb(${colors[color]})` : 'none'};
22 | color: ${({ outline, color }) =>
23 | outline ? `rgb(${colors[color]})` : `rgb(${colors.white})`};
24 | box-shadow: ${({ outline }) => (outline ? 'none' : `${shadows.soft}`)};
25 | border-radius: 8px;
26 | font-size: ${fonts.size.h6};
27 | font-weight: ${fonts.weight.semibold};
28 | padding: ${({ icon, left }) =>
29 | icon ? (left ? '8px 12px' : '7px 28px 8px 12px') : '8px 12px'};
30 | cursor: ${({ disabled }) => (disabled ? 'auto' : 'pointer')};
31 | will-change: transform;
32 |
33 | ${({ isModalButton }) =>
34 | isModalButton &&
35 | css`
36 | display: inline-flex;
37 | align-items: center;
38 | flex-shrink: 0;
39 | `};
40 |
41 | &:disabled {
42 | opacity: 0.6;
43 | box-shadow: ${({ outline }) => (outline ? 'none' : `${shadows.soft}`)};
44 | }
45 |
46 | @media (hover: hover) {
47 | &:hover {
48 | transform: ${({ disabled }) => (!disabled ? 'translateY(-1px)' : 'none')};
49 | background-color: ${({ disabled, hoverColor, color }) =>
50 | !disabled
51 | ? hoverColor
52 | ? `rgb(${colors[hoverColor]})`
53 | : `rgb(${colors[color]})`
54 | : `rgb(${colors[color]})`};
55 | box-shadow: ${({ disabled, outline }) =>
56 | !disabled
57 | ? outline
58 | ? 'none'
59 | : `${shadows.hover}`
60 | : `${shadows.soft}`};
61 | }
62 | }
63 |
64 | &:active {
65 | transform: ${({ disabled }) => (!disabled ? 'translateY(1px)' : 'none')};
66 | background-color: ${({ disabled, activeColor, color }) =>
67 | !disabled
68 | ? activeColor
69 | ? `rgb(${colors[activeColor]})`
70 | : `rgb(${colors[color]})`
71 | : `rgb(${colors[color]})`};
72 | box-shadow: ${({ outline }) => (outline ? 'none' : `${shadows.soft}`)};
73 | color: ${({ outline, color }) =>
74 | outline ? `rgb(${colors[color]})` : `rgba(${colors.whiteTransparent})`};
75 |
76 | & ${StyledIcon} {
77 | opacity: 0.8;
78 | }
79 | }
80 |
81 | & ${StyledIcon} {
82 | display: ${({ icon }) => (icon ? 'inline-block' : 'none')};
83 | mask: ${({ icon }) => (icon ? `url(${icon}) center no-repeat` : 'none')};
84 | background-color: ${({ outline, color }) =>
85 | outline ? `rgb(${colors[color]})` : `rgb(${colors.white})`};
86 | transition: 0.15s ease;
87 | vertical-align: middle;
88 | }
89 | `;
90 |
91 | const Button = ({
92 | children,
93 | fetching,
94 | outline,
95 | type,
96 | color,
97 | hoverColor,
98 | activeColor,
99 | disabled,
100 | icon,
101 | left,
102 | round,
103 | ...props
104 | }) => (
105 |
116 |
117 | {fetching ? (
118 |
119 | ) : (
120 | children
121 | )}
122 |
123 | );
124 |
125 | Button.propTypes = {
126 | children: PropTypes.node.isRequired,
127 | fetching: PropTypes.bool,
128 | outline: PropTypes.bool,
129 | type: PropTypes.string,
130 | color: PropTypes.string,
131 | hoverColor: PropTypes.string,
132 | activeColor: PropTypes.string,
133 | disabled: PropTypes.bool,
134 | icon: PropTypes.any,
135 | left: PropTypes.bool,
136 | };
137 |
138 | Button.defaultProps = {
139 | fetching: false,
140 | outline: false,
141 | type: 'button',
142 | color: 'darkGrey',
143 | hoverColor: 'darkGrey',
144 | activeColor: 'darkGrey',
145 | disabled: false,
146 | icon: null,
147 | left: false,
148 | };
149 |
150 | export default Button;
151 |
--------------------------------------------------------------------------------
/src/handlers/ledger-eth.js:
--------------------------------------------------------------------------------
1 | import EthereumTx from 'ethereumjs-tx';
2 | import TransportU2F from '@ledgerhq/hw-transport-u2f';
3 | import AppEth from '@ledgerhq/hw-app-eth';
4 | import ethereumNetworks from '../references/ethereum-networks.json';
5 | import { removeHexPrefix, getDerivationPathComponents } from 'balance-common';
6 |
7 | /**
8 | * @desc Ledger ETH App instance
9 | */
10 | export let ledgerEthInstance = {
11 | path: "44'/60'/0'/0",
12 | length: 20,
13 | accounts: [],
14 | networkId: 1,
15 | getTransport: () => TransportU2F.create(),
16 | transport: null,
17 | eth: null,
18 | };
19 |
20 | /**
21 | * @desc init Ledger ETH App instance
22 | * @param {String} [network='mainnet']
23 | * @return {Object}
24 | */
25 | export const ledgerEthInit = async (network = 'mainnet') => {
26 | try {
27 | const networkId = ethereumNetworks[network].id;
28 | const transport = await ledgerEthInstance.getTransport();
29 | const eth = new AppEth(transport);
30 | ledgerEthInstance.networkId = networkId;
31 | ledgerEthInstance.transport = transport;
32 | ledgerEthInstance.eth = eth;
33 | return ledgerEthInstance;
34 | } catch (error) {
35 | throw error;
36 | }
37 | };
38 |
39 | /**
40 | * @desc Ledger ETH App get accounts
41 | * @return {Array}
42 | */
43 | export const ledgerEthAccounts = async () => {
44 | const transport = await ledgerEthInstance.getTransport();
45 | try {
46 | const accounts = [];
47 | const pathComponents = getDerivationPathComponents(ledgerEthInstance.path);
48 | for (let i = 0; i < ledgerEthInstance.length; i++) {
49 | const path = `${pathComponents.basePath}${pathComponents.index + i}`;
50 | const address = await ledgerEthInstance.eth.getAddress(path);
51 | address.path = path;
52 | accounts.push(address);
53 | }
54 | ledgerEthInstance.accounts = accounts;
55 | return accounts;
56 | } finally {
57 | transport.close();
58 | }
59 | };
60 |
61 | /**
62 | * @desc Ledget ETH App sign transaction
63 | * @param {Object} transaction { tokenObject, from, to, amount, gasPrice }
64 | * @return {String}
65 | */
66 | export const ledgerEthSignTransaction = async transaction => {
67 | let accounts = ledgerEthInstance.accounts;
68 | if (!accounts.length) {
69 | accounts = await ledgerEthAccounts();
70 | }
71 | const account = accounts.filter(
72 | account => account.address.toLowerCase() === transaction.from.toLowerCase(),
73 | )[0];
74 | if (!account) throw new Error("address unknown '" + transaction.from + "'");
75 | const path = account.path;
76 | const transport = await ledgerEthInstance.getTransport();
77 | try {
78 | const tx = new EthereumTx(transaction);
79 |
80 | tx.raw[6] = Buffer.from([ledgerEthInstance.networkId]); // v
81 | tx.raw[7] = Buffer.from([]); // r
82 | tx.raw[8] = Buffer.from([]); // s
83 | const result = await ledgerEthInstance.eth.signTransaction(
84 | path,
85 | tx.serialize().toString('hex'),
86 | );
87 |
88 | tx.v = Buffer.from(result.v, 'hex');
89 | tx.r = Buffer.from(result.r, 'hex');
90 | tx.s = Buffer.from(result.s, 'hex');
91 |
92 | const signedChainId = Math.floor((tx.v[0] - 35) / 2);
93 | const validChainId = ledgerEthInstance.networkId & 0xff; // FIXME this is to fixed a current workaround that app don't support > 0xff
94 | if (signedChainId !== validChainId) {
95 | throw new Error(
96 | 'Invalid ledgerEthInstance.networkId signature returned. Expected: ' +
97 | ledgerEthInstance.networkId +
98 | ', Got: ' +
99 | signedChainId,
100 | );
101 | }
102 |
103 | return `0x${tx.serialize().toString('hex')}`;
104 | } finally {
105 | transport.close();
106 | }
107 | };
108 |
109 | /**
110 | * @desc Ledget ETH App sign personal message
111 | * @param {Object} message
112 | * @return {String}
113 | */
114 | export const signPersonalMessage = async message => {
115 | let accounts = ledgerEthInstance.accounts;
116 | if (!accounts.length) {
117 | accounts = await ledgerEthAccounts();
118 | }
119 | const account = accounts.filter(
120 | account => account.address.toLowerCase() === message.from.toLowerCase(),
121 | )[0];
122 | if (!account) throw new Error("address unknown '" + message.from + "'");
123 | const path = account.path;
124 | const transport = await ledgerEthInstance.getTransport();
125 | try {
126 | const result = await ledgerEthInstance.eth.signPersonalMessage(
127 | path,
128 | removeHexPrefix(message.data),
129 | );
130 | const v = parseInt(result.v, 10) - 27;
131 | let vHex = v.toString(16);
132 | if (vHex.length < 2) {
133 | vHex = `0${v}`;
134 | }
135 | return `0x${result.r}${result.s}${vHex}`;
136 | } finally {
137 | transport.close();
138 | }
139 | };
140 |
--------------------------------------------------------------------------------
/src/assets/trezor-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/reducers/_metamask.js:
--------------------------------------------------------------------------------
1 | import { parseError } from 'balance-common';
2 | import { modalClose } from './_modal';
3 | import {
4 | accountUpdateAccountAddress,
5 | accountUpdateNetwork,
6 | } from 'balance-common';
7 | import { notificationShow } from './_notification';
8 | import networkList from '../references/ethereum-networks.json';
9 |
10 | // -- Constants ------------------------------------------------------------- //
11 | const METAMASK_CONNECT_REQUEST = 'metamask/METAMASK_CONNECT_REQUEST';
12 | const METAMASK_CONNECT_SUCCESS = 'metamask/METAMASK_CONNECT_SUCCESS';
13 | const METAMASK_CONNECT_FAILURE = 'metamask/METAMASK_CONNECT_FAILURE';
14 |
15 | const METAMASK_NOT_AVAILABLE = 'metamask/METAMASK_NOT_AVAILABLE';
16 | const METAMASK_CLEAR_STATE = 'metamask/METAMASK_CLEAR_STATE';
17 |
18 | const METAMASK_UPDATE_METAMASK_ACCOUNT =
19 | 'metamask/METAMASK_UPDATE_METAMASK_ACCOUNT';
20 |
21 | // -- Actions --------------------------------------------------------------- //
22 |
23 | let accountInterval = null;
24 |
25 | /**
26 | * @desc get metmask selected network
27 | * @return {Promise}
28 | */
29 | const getMetamaskNetwork = () =>
30 | new Promise((resolve, reject) => {
31 | Promise.resolve(window.web3 || window.ethereum)
32 | .then(() => {
33 | return window.web3.eth.defaultAccount || window.ethereum.enable();
34 | })
35 | .then(() => {
36 | window.web3.version.getNetwork((err, networkID) => {
37 | if (err) {
38 | console.error(err);
39 | reject(err);
40 | }
41 | let networkIDList = {};
42 | Object.keys(networkList).forEach(network => {
43 | networkIDList[networkList[network].id] = network;
44 | });
45 | resolve(networkIDList[Number(networkID)] || null);
46 | });
47 | })
48 | .catch(err => {
49 | console.error(err);
50 | reject();
51 | });
52 | });
53 |
54 | export const metamaskUpdateMetamaskAccount = () => (dispatch, getState) => {
55 | if (window.web3.eth.defaultAccount !== getState().metamask.accountAddress) {
56 | const accountAddress = window.web3.eth.defaultAccount;
57 | dispatch(modalClose());
58 | dispatch({
59 | type: METAMASK_UPDATE_METAMASK_ACCOUNT,
60 | payload: accountAddress,
61 | });
62 | if (accountAddress)
63 | dispatch(accountUpdateAccountAddress(accountAddress, 'METAMASK'));
64 | }
65 | };
66 |
67 | export const metamaskConnectInit = () => (dispatch, getState) => {
68 | const accountAddress = getState().metamask.accountAddress;
69 | if (accountAddress)
70 | dispatch(accountUpdateAccountAddress(accountAddress, 'METAMASK'));
71 | dispatch({ type: METAMASK_CONNECT_REQUEST });
72 | if (window.ethereum || window.web3) {
73 | getMetamaskNetwork()
74 | .then(network => {
75 | dispatch({ type: METAMASK_CONNECT_SUCCESS, payload: network });
76 | dispatch(accountUpdateNetwork(network));
77 | dispatch(metamaskUpdateMetamaskAccount());
78 | accountInterval = setInterval(
79 | () => dispatch(metamaskUpdateMetamaskAccount()),
80 | 100,
81 | );
82 | })
83 | .catch(error => {
84 | const message = parseError(error);
85 | dispatch(notificationShow(message, true));
86 | dispatch({ type: METAMASK_CONNECT_FAILURE });
87 | });
88 | } else {
89 | dispatch({ type: METAMASK_NOT_AVAILABLE });
90 | }
91 | };
92 |
93 | export const metamaskClearState = () => dispatch => {
94 | clearInterval(accountInterval);
95 | dispatch({ type: METAMASK_CLEAR_STATE });
96 | };
97 |
98 | export const metamaskClearIntervals = () => dispatch => {
99 | clearInterval(accountInterval);
100 | };
101 |
102 | // -- Reducer --------------------------------------------------------------- //
103 | const INITIAL_STATE = {
104 | fetching: false,
105 | accountAddress: '',
106 | web3Available: false,
107 | network: 'mainnet',
108 | };
109 |
110 | export default (state = INITIAL_STATE, action) => {
111 | switch (action.type) {
112 | case METAMASK_CONNECT_REQUEST:
113 | return {
114 | ...state,
115 | fetching: true,
116 | web3Available: false,
117 | };
118 | case METAMASK_CONNECT_SUCCESS:
119 | return {
120 | ...state,
121 | fetching: false,
122 | web3Available: true,
123 | network: action.payload,
124 | };
125 | case METAMASK_CONNECT_FAILURE:
126 | return {
127 | ...state,
128 | fetching: false,
129 | web3Available: true,
130 | };
131 | case METAMASK_NOT_AVAILABLE:
132 | return {
133 | ...state,
134 | fetching: false,
135 | web3Available: false,
136 | };
137 | case METAMASK_UPDATE_METAMASK_ACCOUNT:
138 | return {
139 | ...state,
140 | accountAddress: action.payload,
141 | };
142 | case METAMASK_CLEAR_STATE:
143 | return {
144 | ...state,
145 | ...INITIAL_STATE,
146 | };
147 | default:
148 | return state;
149 | }
150 | };
151 |
--------------------------------------------------------------------------------