From 7627aade89d0e08e4329c3a7ead1c944d4cd21d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Volek?= Date: Mon, 30 Jul 2018 12:52:13 +0200 Subject: [PATCH] Fixed some eslint problems --- .eslintignore | 1 + package.json | 2 +- src/babel/babel-plugin-import.js | 22 +- src/flowtype/css.js | 2 +- src/flowtype/index.js | 16 +- src/flowtype/npm/bignumber.js | 10 +- src/flowtype/npm/ethereum-types.js | 23 +- src/flowtype/npm/react-redux_v5.x.x.js | 16 +- src/flowtype/npm/react-router-dom_v4.x.x.js | 44 +- src/flowtype/npm/react-router-redux.js | 13 +- src/flowtype/npm/react-router_v4.x.x.js | 34 +- src/flowtype/npm/redux_v3.x.x.js | 3 +- src/flowtype/npm/web3.js | 8 +- src/js/actions/AccountsActions.js | 37 +- src/js/actions/DiscoveryActions.js | 513 ++++---- src/js/actions/HistoryActions.js | 1 - src/js/actions/LocalStorageActions.js | 122 +- src/js/actions/LogActions.js | 49 +- src/js/actions/ModalActions.js | 166 ++- src/js/actions/NotificationActions.js | 50 +- src/js/actions/PendingTxActions.js | 4 +- src/js/actions/ReceiveActions.js | 138 +-- src/js/actions/SelectedAccountActions.js | 173 ++- src/js/actions/SendFormActions.js | 1062 ++++++++--------- src/js/actions/SessionStorageActions.js | 53 +- src/js/actions/SummaryActions.js | 44 +- src/js/actions/TokenActions.js | 149 ++- src/js/actions/TrezorConnectActions.js | 491 ++++---- src/js/actions/WalletActions.js | 128 +- src/js/actions/Web3Actions.js | 329 +++-- src/js/actions/constants/TrezorConnect.js | 2 +- src/js/actions/constants/account.js | 2 +- src/js/actions/constants/discovery.js | 2 +- src/js/actions/constants/localStorage.js | 2 +- src/js/actions/constants/log.js | 2 +- src/js/actions/constants/modal.js | 2 +- src/js/actions/constants/notification.js | 2 +- src/js/actions/constants/pendingTx.js | 2 +- src/js/actions/constants/receive.js | 2 +- src/js/actions/constants/send.js | 2 +- src/js/actions/constants/summary.js | 2 +- src/js/actions/constants/token.js | 2 +- src/js/actions/constants/wallet.js | 2 +- src/js/actions/constants/web3.js | 2 +- src/js/components/common/Footer.js | 38 +- src/js/components/common/Header.js | 44 +- src/js/components/common/LoaderCircle.js | 15 +- src/js/components/common/Log.js | 33 +- src/js/components/common/Notification.js | 72 +- src/js/components/landing/ConnectDevice.js | 82 +- src/js/components/landing/InstallBridge.js | 28 +- src/js/components/landing/LandingPage.js | 61 +- .../components/landing/LocalStorageError.js | 12 +- src/js/components/landing/Preloader.js | 14 +- .../components/landing/TrezorConnectError.js | 12 +- src/js/components/landing/index.js | 34 +- src/js/components/modal/ConfirmAddress.js | 33 +- src/js/components/modal/ConfirmSignTx.js | 9 +- src/js/components/modal/DuplicateDevice.js | 45 +- src/js/components/modal/InvalidPin.js | 6 +- src/js/components/modal/Passphrase.js | 98 +- src/js/components/modal/PassphraseType.js | 5 +- src/js/components/modal/Pin.js | 79 +- src/js/components/modal/RememberDevice.js | 28 +- src/js/components/modal/index.js | 112 +- .../components/wallet/account/AccountTabs.js | 27 +- .../wallet/account/SelectedAccount.js | 95 +- .../wallet/account/receive/Receive.js | 50 +- .../wallet/account/receive/index.js | 32 +- .../wallet/account/send/AdvancedForm.js | 101 +- .../wallet/account/send/CoinSelectOption.js | 16 +- .../wallet/account/send/FeeSelect.js | 32 +- .../account/send/PendingTransactions.js | 32 +- .../wallet/account/send/SendForm.js | 108 +- .../components/wallet/account/send/index.js | 34 +- .../wallet/account/sign/SignVerify.js | 48 +- .../wallet/account/summary/Summary.js | 110 +- .../wallet/account/summary/SummaryDetails.js | 24 +- .../wallet/account/summary/SummaryTokens.js | 23 +- .../wallet/account/summary/index.js | 46 +- .../wallet/aside/AccountSelection.js | 53 +- src/js/components/wallet/aside/Aside.js | 56 +- .../components/wallet/aside/CoinSelection.js | 16 +- .../wallet/aside/DeviceSelection.js | 107 +- .../wallet/aside/StickyContainer.js | 65 +- src/js/components/wallet/aside/index.js | 54 +- src/js/components/wallet/index.js | 66 +- src/js/components/wallet/pages/Acquire.js | 36 +- src/js/components/wallet/pages/Bootloader.js | 20 +- src/js/components/wallet/pages/Dashboard.js | 24 +- .../components/wallet/pages/DeviceSettings.js | 22 +- .../wallet/pages/DeviceSettingsTabs.js | 5 +- src/js/components/wallet/pages/Initialize.js | 22 +- .../components/wallet/pages/WalletSettings.js | 12 +- src/js/index.js | 6 +- src/js/reducers/AccountsReducer.js | 63 +- src/js/reducers/DevicesReducer.js | 141 +-- src/js/reducers/DiscoveryReducer.js | 91 +- src/js/reducers/FiatRateReducer.js | 17 +- src/js/reducers/LocalStorageReducer.js | 31 +- src/js/reducers/LogReducer.js | 25 +- src/js/reducers/ModalReducer.js | 63 +- src/js/reducers/NotificationReducer.js | 24 +- src/js/reducers/PendingTxReducer.js | 35 +- src/js/reducers/ReceiveReducer.js | 31 +- src/js/reducers/SelectedAccountReducer.js | 17 +- src/js/reducers/SendFormReducer.js | 78 +- src/js/reducers/SummaryReducer.js | 23 +- src/js/reducers/TokensReducer.js | 49 +- src/js/reducers/TrezorConnectReducer.js | 42 +- src/js/reducers/WalletReducer.js | 57 +- src/js/reducers/Web3Reducer.js | 29 +- src/js/reducers/index.js | 6 +- src/js/reducers/utils/index.js | 82 +- src/js/router/index.js | 36 +- src/js/services/CoinmarketcapService.js | 54 +- src/js/services/LocalStorageService.js | 98 +- src/js/services/LogService.js | 15 +- src/js/services/RouterService.js | 49 +- src/js/services/TrezorConnectService.js | 46 +- src/js/services/WalletService.js | 23 +- src/js/services/index.js | 2 +- src/js/store/index.js | 25 +- src/js/utils/ethUtils.js | 26 +- src/js/utils/formatUtils.js | 38 +- src/js/utils/networkUtils.js | 14 +- src/js/utils/promiseUtils.js | 2 +- src/js/utils/windowUtils.js | 13 +- 128 files changed, 3398 insertions(+), 3880 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..813e13d4 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +solidity \ No newline at end of file diff --git a/package.json b/package.json index 205ff8b6..cd393092 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build": "rm -rf build && webpack --config ./webpack/config.prod.babel.js --progress", "flow": "flow check src/js", "test": "", - "lint": "npx eslint ./" + "lint": "npx eslint ./ --fix" }, "dependencies": { "color-hash": "^1.0.3", diff --git a/src/babel/babel-plugin-import.js b/src/babel/babel-plugin-import.js index 8e2c839c..adaefac9 100644 --- a/src/babel/babel-plugin-import.js +++ b/src/babel/babel-plugin-import.js @@ -8,27 +8,27 @@ const replacePrefix = (path, opts = [], sourceFile) => { const options = [].concat(opts); if (typeof path === 'string') { if (path.indexOf('~/') === 0) { - return path.replace('~/', `${ cwd }/src/`); + return path.replace('~/', `${cwd}/src/`); } } return path; -} +}; -export default ({ 'types': t }) => { +export default ({ types: t }) => { const visitor = { CallExpression(path, state) { if (path.node.callee.name !== 'require') { return; } - + const args = path.node.arguments; if (!args.length) { return; } - + const firstArg = traverseExpression(t, args[0]); - + if (firstArg) { firstArg.value = replacePrefix(firstArg.value, state.opts, state.file.opts.filename); } @@ -45,14 +45,14 @@ export default ({ 'types': t }) => { if (path.node.source) { path.node.source.value = replacePrefix(path.node.source.value, state.opts, state.file.opts.filename); } - } + }, }; return { - 'visitor': { + visitor: { Program(path, state) { path.traverse(visitor, state); - } - } + }, + }, }; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/flowtype/css.js b/src/flowtype/css.js index 624e2f4a..b06105e3 100644 --- a/src/flowtype/css.js +++ b/src/flowtype/css.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + declare module CSSModule { declare var exports: { [key: string]: string }; diff --git a/src/flowtype/index.js b/src/flowtype/index.js index 5f89d8af..a66b4135 100644 --- a/src/flowtype/index.js +++ b/src/flowtype/index.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import type { Store as ReduxStore, @@ -9,7 +9,7 @@ import type { ThunkAction as ReduxThunkAction, AsyncAction as ReduxAsyncAction, ThunkDispatch as ReduxThunkDispatch, - PlainDispatch as ReduxPlainDispatch + PlainDispatch as ReduxPlainDispatch, } from 'redux'; import type { Reducers, ReducersState } from '~/js/reducers'; @@ -43,7 +43,7 @@ import type { import type { RouterAction, LocationState } from 'react-router-redux'; export type TrezorDevice = { - remember: boolean; // device should be remembered + remember: boolean; // device should be remembered connected: boolean; // device is connected available: boolean; // device cannot be used because of features.passphrase_protection is different then expected path: string; @@ -87,19 +87,19 @@ type IFrameHandshake = { payload: any } -export type Action = +export type Action = RouterAction | IFrameHandshake | TransportEventAction | DeviceEventAction | UiEventAction - + | SelectedAccountAction | AccountAction | DiscoveryAction - | StorageAction - | LogAction - | ModalAction + | StorageAction + | LogAction + | ModalAction | NotificationAction | PendingTxAction | ReceiveAction diff --git a/src/flowtype/npm/bignumber.js b/src/flowtype/npm/bignumber.js index 488831aa..5fa3a6fe 100644 --- a/src/flowtype/npm/bignumber.js +++ b/src/flowtype/npm/bignumber.js @@ -7,22 +7,22 @@ declare module 'bignumber.js' { declare type ROUND_HALF_EVEN = 2 declare type ROUND_UP = 3 declare type RM = ROUND_DOWN | ROUND_HALF_UP | ROUND_HALF_EVEN | ROUND_UP - + declare class T_BigNumber { // Properties static DP: number; static RM: RM; static E_NEG: number; static E_POS: number; - + c: Array; e: number; s: -1 | 1; - + // Constructors static (value: $npm$big$number$object): T_BigNumber; constructor(value: $npm$big$number$object): T_BigNumber; - + // Methods abs(): BigNumber; cmp(n: $npm$big$number$object): $npm$cmp$result; @@ -51,7 +51,7 @@ declare module 'bignumber.js' { valueOf(): string; toJSON(): string; } - + //declare module.exports: typeof T_BigNumber declare export default typeof T_BigNumber; } \ No newline at end of file diff --git a/src/flowtype/npm/ethereum-types.js b/src/flowtype/npm/ethereum-types.js index fa0a0aeb..4bdd96f5 100644 --- a/src/flowtype/npm/ethereum-types.js +++ b/src/flowtype/npm/ethereum-types.js @@ -16,16 +16,16 @@ declare module 'ethereum-types' { | 'mether' | 'gether' | 'tether' - + declare export type EthereumAddressT = string declare export type EthereumBlockNumberT = number declare export type EthereumBlockHashT = string declare export type EthereumTransactionHashT = string // end data types - + // start contract types declare export type EthereumWatchErrorT = ?Object - + declare export type EthereumEventT = { address: EthereumAddressT, args: A, @@ -38,35 +38,32 @@ declare module 'ethereum-types' { transactionLogIndex: string, type: 'mined' // TODO: what other types are there? } - + // this represents the setup object returned from truffle-contract // we use it to get a known contact `at(address)` (ie. for POATokenContract addresses) declare export type EthereumContractSetupT = { at: EthereumAddressT => Promise } - + declare export type EthereumSendTransactionOptionsT = { from: EthereumAddressT, gas: number, value?: number } - - declare export type EthereumSendTransactionT = EthereumSendTransactionOptionsT => Promise< - EthereumTransactionHashT - > - + + declare export type EthereumSendTransactionT = EthereumSendTransactionOptionsT => Promise + // TODO(mattgstevens): it would be nice to have an Generic type for a Contract instance // similar to the EthererumWatchEventT // // declare export type SendTransactionContractT = interface .sendTransaction(EthereumAddressT) // declare export type WatchableContractT = (error: Object, response: A) - + // declare export type EthereumContractWatcherT = (options: { // fromBlock?: EthereumBlockNumberT, // toBlock?: EthereumBlockNumberT, // address?: EthereumAddressT // }) => * - + // end contract data } - \ No newline at end of file diff --git a/src/flowtype/npm/react-redux_v5.x.x.js b/src/flowtype/npm/react-redux_v5.x.x.js index b5897b4a..58471492 100644 --- a/src/flowtype/npm/react-redux_v5.x.x.js +++ b/src/flowtype/npm/react-redux_v5.x.x.js @@ -1,9 +1,9 @@ // flow-typed signature: 59b0c4be0e1408f21e2446be96c79804 // flow-typed version: 9092387fd2/react-redux_v5.x.x/flow_>=v0.54.x -import type { Dispatch, Store } from "redux"; +import type { Dispatch, Store } from 'redux'; -declare module "react-redux" { +declare module 'react-redux' { /* S = State @@ -32,9 +32,7 @@ declare module "react-redux" { declare type Context = { store: Store<*, *> }; - declare type ComponentWithDefaultProps = Class< - React$Component - > & { defaultProps: DP }; + declare type ComponentWithDefaultProps = Class> & { defaultProps: DP }; declare class ConnectedComponentWithDefaultProps< OP, @@ -55,13 +53,9 @@ declare module "react-redux" { state: void } - declare type ConnectedComponentWithDefaultPropsClass = Class< - ConnectedComponentWithDefaultProps - >; + declare type ConnectedComponentWithDefaultPropsClass = Class>; - declare type ConnectedComponentClass = Class< - ConnectedComponent - >; + declare type ConnectedComponentClass = Class>; declare type Connector = (( component: ComponentWithDefaultProps diff --git a/src/flowtype/npm/react-router-dom_v4.x.x.js b/src/flowtype/npm/react-router-dom_v4.x.x.js index 304f54d7..8d0b48ba 100644 --- a/src/flowtype/npm/react-router-dom_v4.x.x.js +++ b/src/flowtype/npm/react-router-dom_v4.x.x.js @@ -1,4 +1,4 @@ -declare module "react-router-dom" { +declare module 'react-router-dom' { declare export class BrowserRouter extends React$Component<{ basename?: string, forceRefresh?: boolean, @@ -6,21 +6,21 @@ declare module "react-router-dom" { keyLength?: number, children?: React$Node }> {} - + declare export class HashRouter extends React$Component<{ basename?: string, getUserConfirmation?: GetUserConfirmation, hashType?: "slash" | "noslash" | "hashbang", children?: React$Node }> {} - + declare export class Link extends React$Component<{ className?: string, to: string | LocationShape, replace?: boolean, children?: React$Node }> {} - + declare export class NavLink extends React$Component<{ to: string | LocationShape, activeClassName?: string, @@ -32,7 +32,7 @@ declare module "react-router-dom" { exact?: boolean, strict?: boolean }> {} - + // NOTE: Below are duplicated from react-router. If updating these, please // update the react-router and react-router-native types as well. declare export type Location = { @@ -42,16 +42,16 @@ declare module "react-router-dom" { state?: any, key?: string }; - + declare export type LocationShape = { pathname?: string, search?: string, hash?: string, state?: any }; - + declare export type HistoryAction = "PUSH" | "REPLACE" | "POP"; - + declare export type RouterHistory = { length: number, location: Location, @@ -72,37 +72,37 @@ declare module "react-router-dom" { index?: number, entries?: Array }; - + declare export type Match = { params: { [key: string]: ?string }, isExact: boolean, path: string, url: string }; - + declare export type ContextRouter = {| history: RouterHistory, location: Location, match: Match, staticContext?: StaticRouterContext, |}; - + declare export type GetUserConfirmation = ( message: string, callback: (confirmed: boolean) => void ) => void; - + declare type StaticRouterContext = { url?: string }; - + declare export class StaticRouter extends React$Component<{ basename?: string, location?: string | Location, context: StaticRouterContext, children?: React$Node }> {} - + declare export class MemoryRouter extends React$Component<{ initialEntries?: Array, initialIndex?: number, @@ -110,22 +110,22 @@ declare module "react-router-dom" { keyLength?: number, children?: React$Node }> {} - + declare export class Router extends React$Component<{ history: RouterHistory, children?: React$Node }> {} - + declare export class Prompt extends React$Component<{ message: string | ((location: Location) => string | boolean), when?: boolean }> {} - + declare export class Redirect extends React$Component<{ to: string | LocationShape, push?: boolean }> {} - + declare export class Route extends React$Component<{ component?: React$ComponentType<*>, render?: (router: ContextRouter) => React$Node, @@ -134,22 +134,22 @@ declare module "react-router-dom" { exact?: boolean, strict?: boolean }> {} - + declare export class Switch extends React$Component<{ children?: React$Node }> {} - + declare export function withRouter

( Component: React$ComponentType<{| ...ContextRouter, ...P |}> ): React$ComponentType

; - + declare type MatchPathOptions = { path?: string, exact?: boolean, sensitive?: boolean, strict?: boolean }; - + declare export function matchPath( pathname: string, options?: MatchPathOptions | string diff --git a/src/flowtype/npm/react-router-redux.js b/src/flowtype/npm/react-router-redux.js index b04d0640..81c9a297 100644 --- a/src/flowtype/npm/react-router-redux.js +++ b/src/flowtype/npm/react-router-redux.js @@ -1,12 +1,11 @@ -import type { +import type { RouterHistory, - Location as RouterLocation + Location as RouterLocation, } from 'react-router'; -declare module "react-router-redux" { - +declare module 'react-router-redux' { // custom state for location - declare export type LocationState = {[key: string] : string}; + declare export type LocationState = {[key: string]: string}; declare export type Location = { pathname: string, @@ -16,7 +15,7 @@ declare module "react-router-redux" { state: LocationState } - declare export var LOCATION_CHANGE: "@@router/LOCATION_CHANGE"; + declare export var LOCATION_CHANGE: "@@router/LOCATION_CHANGE"; declare export type RouterAction = { type: typeof LOCATION_CHANGE, @@ -32,7 +31,7 @@ declare module "react-router-redux" { declare export function go(a: string): RouterAction; declare export function goBack(): RouterAction; declare export function goForward(): RouterAction; - + //declare export function routerReducer(state?: S, action: A): S; declare export function routerReducer(state?: State, action: any): State; declare export function routerMiddleware(history: any): any; diff --git a/src/flowtype/npm/react-router_v4.x.x.js b/src/flowtype/npm/react-router_v4.x.x.js index 86489aae..a115b15a 100644 --- a/src/flowtype/npm/react-router_v4.x.x.js +++ b/src/flowtype/npm/react-router_v4.x.x.js @@ -1,4 +1,4 @@ -declare module "react-router" { +declare module 'react-router' { // NOTE: many of these are re-exported by react-router-dom and // react-router-native, so when making changes, please be sure to update those // as well. @@ -9,16 +9,16 @@ declare module "react-router" { state?: any, key?: string }; - + declare export type LocationShape = { pathname?: string, search?: string, hash?: string, state?: any }; - + declare export type HistoryAction = "PUSH" | "REPLACE" | "POP"; - + declare export type RouterHistory = { length: number, location: Location, @@ -39,37 +39,37 @@ declare module "react-router" { index?: number, entries?: Array }; - + declare export type Match = { params: { [key: string]: ?string }, isExact: boolean, path: string, url: string }; - + declare export type ContextRouter = {| history: RouterHistory, location: Location, match: Match, staticContext?: StaticRouterContext |}; - + declare export type GetUserConfirmation = ( message: string, callback: (confirmed: boolean) => void ) => void; - + declare type StaticRouterContext = { url?: string }; - + declare export class StaticRouter extends React$Component<{ basename?: string, location?: string | Location, context: StaticRouterContext, children?: React$Node }> {} - + declare export class MemoryRouter extends React$Component<{ initialEntries?: Array, initialIndex?: number, @@ -77,22 +77,22 @@ declare module "react-router" { keyLength?: number, children?: React$Node }> {} - + declare export class Router extends React$Component<{ history: RouterHistory, children?: React$Node }> {} - + declare export class Prompt extends React$Component<{ message: string | ((location: Location) => string | true), when?: boolean }> {} - + declare export class Redirect extends React$Component<{ to: string | LocationShape, push?: boolean }> {} - + declare export class Route extends React$Component<{ component?: React$ComponentType<*>, render?: (router: ContextRouter) => React$Node, @@ -101,15 +101,15 @@ declare module "react-router" { exact?: boolean, strict?: boolean }> {} - + declare export class Switch extends React$Component<{ children?: React$Node }> {} - + declare export function withRouter

( Component: React$ComponentType<{| ...ContextRouter, ...P |}> ): React$ComponentType

; - + declare type MatchPathOptions = { path?: string, exact?: boolean, diff --git a/src/flowtype/npm/redux_v3.x.x.js b/src/flowtype/npm/redux_v3.x.x.js index d51acdc5..d0717bdf 100644 --- a/src/flowtype/npm/redux_v3.x.x.js +++ b/src/flowtype/npm/redux_v3.x.x.js @@ -1,5 +1,4 @@ declare module 'redux' { - /* S = State @@ -19,7 +18,7 @@ declare module 'redux' { declare export type AsyncDispatch = (action: AsyncAction) => Promise; declare export type PlainDispatch}> = DispatchAPI; /* NEW: Dispatch is now a combination of these different dispatch types */ - declare export type ReduxDispatch = PlainDispatch & ThunkDispatch & AsyncDispatch; + declare export type ReduxDispatch = PlainDispatch & ThunkDispatch & AsyncDispatch; declare export type MiddlewareAPI = { // dispatch: Dispatch; diff --git a/src/flowtype/npm/web3.js b/src/flowtype/npm/web3.js index 8af20550..81e27543 100644 --- a/src/flowtype/npm/web3.js +++ b/src/flowtype/npm/web3.js @@ -33,7 +33,7 @@ declare module 'web3' { network: string; // and many more } - + } declare export type EstimateGasOptions = { @@ -124,10 +124,8 @@ declare module 'web3' { } - -// -// - +// +// /*declare module 'web3' { diff --git a/src/js/actions/AccountsActions.js b/src/js/actions/AccountsActions.js index 74b8a99e..19ab2af7 100644 --- a/src/js/actions/AccountsActions.js +++ b/src/js/actions/AccountsActions.js @@ -1,5 +1,4 @@ /* @flow */ -'use strict'; import * as ACCOUNT from './constants/account'; import type { Action, TrezorDevice } from '~/flowtype'; @@ -22,7 +21,7 @@ export type AccountCreateAction = { network: string, index: number, path: Array, - address: string + address: string } export type AccountSetBalanceAction = { @@ -41,22 +40,18 @@ export type AccountSetNonceAction = { nonce: number } -export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => { - return { - type: ACCOUNT.SET_BALANCE, - address, - network, - deviceState, - balance - } -} - -export const setNonce = (address: string, network: string, deviceState: string, nonce: number): Action => { - return { - type: ACCOUNT.SET_NONCE, - address, - network, - deviceState, - nonce - } -} \ No newline at end of file +export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => ({ + type: ACCOUNT.SET_BALANCE, + address, + network, + deviceState, + balance, +}); + +export const setNonce = (address: string, network: string, deviceState: string, nonce: number): Action => ({ + type: ACCOUNT.SET_NONCE, + address, + network, + deviceState, + nonce, +}); \ No newline at end of file diff --git a/src/js/actions/DiscoveryActions.js b/src/js/actions/DiscoveryActions.js index 1b40ccda..57c857c3 100644 --- a/src/js/actions/DiscoveryActions.js +++ b/src/js/actions/DiscoveryActions.js @@ -1,19 +1,21 @@ /* @flow */ -'use strict'; import TrezorConnect from 'trezor-connect'; +import HDKey from 'hdkey'; +import EthereumjsUtil from 'ethereumjs-util'; import * as DISCOVERY from './constants/discovery'; import * as ACCOUNT from './constants/account'; import * as TOKEN from './constants/token'; import * as NOTIFICATION from './constants/notification'; -import * as AccountsActions from '../actions/AccountsActions'; +import * as AccountsActions from './AccountsActions'; -import HDKey from 'hdkey'; -import EthereumjsUtil from 'ethereumjs-util'; import { getNonceAsync, getBalanceAsync, getTokenBalanceAsync } from './Web3Actions'; import { setBalance as setTokenBalance } from './TokenActions'; -import type { ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice } from '~/flowtype'; +import type { + ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice, +} from '~/flowtype'; + import type { Discovery, State } from '../reducers/DiscoveryReducer'; export type DiscoveryAction = { @@ -50,320 +52,297 @@ export type DiscoveryCompleteAction = { network: string } -export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - const selected = getState().wallet.selectedDevice; - if (!selected) { - // TODO: throw error - console.error("Start discovery: no selected device", device) - return; - } else if (selected.path !== device.path) { - console.error("Start discovery: requested device is not selected", device, selected) - return; - } else if (!selected.state) { - console.warn("Start discovery: Selected device wasn't authenticated yet...") - return; - } else if (selected.connected && !selected.available) { - console.warn("Start discovery: Selected device is unavailable...") - return; - } - - const web3 = getState().web3.find(w3 => w3.network === network); - if (!web3) { - console.error("Start discovery: Web3 does not exist", network) - return; - } - - if (!web3.web3.currentProvider.isConnected()) { - console.error("Start discovery: Web3 is not connected", network) - dispatch({ - type: DISCOVERY.WAITING_FOR_BACKEND, - device, - network - }); - return; - } - - const discovery: State = getState().discovery; - let discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network); +export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const selected = getState().wallet.selectedDevice; + if (!selected) { + // TODO: throw error + console.error('Start discovery: no selected device', device); + return; + } if (selected.path !== device.path) { + console.error('Start discovery: requested device is not selected', device, selected); + return; + } if (!selected.state) { + console.warn("Start discovery: Selected device wasn't authenticated yet..."); + return; + } if (selected.connected && !selected.available) { + console.warn('Start discovery: Selected device is unavailable...'); + return; + } - - - if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { - dispatch({ - type: DISCOVERY.WAITING_FOR_DEVICE, - device, - network - }); - return; - } + const web3 = getState().web3.find(w3 => w3.network === network); + if (!web3) { + console.error('Start discovery: Web3 does not exist', network); + return; + } - if (!discoveryProcess) { - dispatch( begin(device, network) ); - return; - } else { - if (discoveryProcess.completed && !ignoreCompleted) { - dispatch({ - type: DISCOVERY.COMPLETE, - device, - network - }); - } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) { - // discovery cycle was interrupted - // start from beginning - dispatch( begin(device, network) ); - } else { - dispatch( discoverAccount(device, discoveryProcess) ); - } - } + if (!web3.web3.currentProvider.isConnected()) { + console.error('Start discovery: Web3 is not connected', network); + dispatch({ + type: DISCOVERY.WAITING_FOR_BACKEND, + device, + network, + }); + return; } -} -const begin = (device: TrezorDevice, network: string): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { + const discovery: State = getState().discovery; + const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network); - const { config } = getState().localStorage; - const coinToDiscover = config.coins.find(c => c.network === network); - if (!coinToDiscover) return; + if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { dispatch({ type: DISCOVERY.WAITING_FOR_DEVICE, device, - network + network, }); + return; + } - // get xpub from TREZOR - const response = await TrezorConnect.getPublicKey({ - device: { - path: device.path, - instance: device.instance, - state: device.state - }, - path: coinToDiscover.bip44, - keepSession: true, // acquire and hold session - useEmptyPassphrase: !device.instance, + if (!discoveryProcess) { + dispatch(begin(device, network)); + } else if (discoveryProcess.completed && !ignoreCompleted) { + dispatch({ + type: DISCOVERY.COMPLETE, + device, + network, }); + } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) { + // discovery cycle was interrupted + // start from beginning + dispatch(begin(device, network)); + } else { + dispatch(discoverAccount(device, discoveryProcess)); + } +}; + +const begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { config } = getState().localStorage; + const coinToDiscover = config.coins.find(c => c.network === network); + if (!coinToDiscover) return; + + dispatch({ + type: DISCOVERY.WAITING_FOR_DEVICE, + device, + network, + }); + + // get xpub from TREZOR + const response = await TrezorConnect.getPublicKey({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + path: coinToDiscover.bip44, + keepSession: true, // acquire and hold session + useEmptyPassphrase: !device.instance, + }); // handle TREZOR response error - if (!response.success) { - // TODO: check message - console.warn("DISCOVERY ERROR", response) + if (!response.success) { + // TODO: check message + console.warn('DISCOVERY ERROR', response); + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Discovery error', + message: response.payload.error, + cancelable: true, + actions: [ + { + label: 'Try again', + callback: () => { + dispatch(start(device, network)); + }, + }, + ], + }, + }); + return; + } + + // check for interruption + const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === device.state && d.network === network); + if (discoveryProcess && discoveryProcess.interrupted) return; + + const basePath: Array = response.payload.path; + + // send data to reducer + dispatch({ + type: DISCOVERY.START, + network: coinToDiscover.network, + device, + publicKey: response.payload.publicKey, + chainCode: response.payload.chainCode, + basePath, + }); + + dispatch(start(device, network)); +}; + +const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const completed: boolean = discoveryProcess.completed; + discoveryProcess.completed = false; + + const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`); + const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex); + const publicAddress: string = EthereumjsUtil.publicToAddress(derivedKey.publicKey, true).toString('hex'); + const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress); + const network = discoveryProcess.network; + + + // TODO: check if address was created before + + // verify address with TREZOR + const verifyAddress = await TrezorConnect.ethereumGetAddress({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + path, + showOnTrezor: false, + keepSession: true, + useEmptyPassphrase: !device.instance, + }); + if (discoveryProcess.interrupted) return; + + // TODO: with block-book (Martin) + // const discoveryA = await TrezorConnect.accountDiscovery({ + // device: { + // path: device.path, + // instance: device.instance, + // state: device.state + // }, + // }); + // if (discoveryProcess.interrupted) return; + + if (verifyAddress && verifyAddress.success) { + //const trezorAddress: string = '0x' + verifyAddress.payload.address; + const trezorAddress: string = EthereumjsUtil.toChecksumAddress(verifyAddress.payload.address); + if (trezorAddress !== ethAddress) { + // throw inconsistent state error + console.warn('Inconsistent state', trezorAddress, ethAddress); + dispatch({ type: NOTIFICATION.ADD, payload: { type: 'error', - title: 'Discovery error', - message: response.payload.error, + title: 'Address validation error', + message: `Addresses are different. TREZOR: ${trezorAddress} HDKey: ${ethAddress}`, cancelable: true, actions: [ { label: 'Try again', callback: () => { - dispatch(start(device, network)) - } - } - ] - } - }) + dispatch(start(device, discoveryProcess.network)); + }, + }, + ], + }, + }); return; } - - // check for interruption - let discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === device.state && d.network === network); - if (discoveryProcess && discoveryProcess.interrupted) return; - - const basePath: Array = response.payload.path; - - // send data to reducer + } else { + // handle TREZOR communication error dispatch({ - type: DISCOVERY.START, - network: coinToDiscover.network, - device, - publicKey: response.payload.publicKey, - chainCode: response.payload.chainCode, - basePath, + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Address validation error', + message: verifyAddress.payload.error, + cancelable: true, + actions: [ + { + label: 'Try again', + callback: () => { + dispatch(start(device, discoveryProcess.network)); + }, + }, + ], + }, }); - - dispatch( start(device, network) ); + return; } -} -const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { + const web3instance = getState().web3.find(w3 => w3.network === network); + if (!web3instance) return; - const completed: boolean = discoveryProcess.completed; - discoveryProcess.completed = false; + const balance = await getBalanceAsync(web3instance.web3, ethAddress); + if (discoveryProcess.interrupted) return; + const nonce: number = await getNonceAsync(web3instance.web3, ethAddress); + if (discoveryProcess.interrupted) return; - const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`); - const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex); - const publicAddress: string = EthereumjsUtil.publicToAddress(derivedKey.publicKey, true).toString('hex'); - const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress); - const network = discoveryProcess.network; + const addressIsEmpty = nonce < 1 && !balance.greaterThan(0); - + if (!addressIsEmpty || (addressIsEmpty && completed) || (addressIsEmpty && discoveryProcess.accountIndex === 0)) { + dispatch({ + type: ACCOUNT.CREATE, + device, + network, + index: discoveryProcess.accountIndex, + path, + address: ethAddress, + }); + dispatch( + AccountsActions.setBalance(ethAddress, network, device.state || 'undefined', web3instance.web3.fromWei(balance.toString(), 'ether')), + ); + dispatch(AccountsActions.setNonce(ethAddress, network, device.state || 'undefined', nonce)); - // TODO: check if address was created before + if (!completed) { dispatch(discoverAccount(device, discoveryProcess)); } + } - // verify address with TREZOR - const verifyAddress = await TrezorConnect.ethereumGetAddress({ + if (addressIsEmpty) { + // release acquired sesssion + await TrezorConnect.getFeatures({ device: { path: device.path, instance: device.instance, - state: device.state + state: device.state, }, - path, - showOnTrezor: false, - keepSession: true, + keepSession: false, useEmptyPassphrase: !device.instance, }); if (discoveryProcess.interrupted) return; - // TODO: with block-book (Martin) - // const discoveryA = await TrezorConnect.accountDiscovery({ - // device: { - // path: device.path, - // instance: device.instance, - // state: device.state - // }, - // }); - // if (discoveryProcess.interrupted) return; - - if (verifyAddress && verifyAddress.success) { - //const trezorAddress: string = '0x' + verifyAddress.payload.address; - const trezorAddress: string = EthereumjsUtil.toChecksumAddress(verifyAddress.payload.address); - if (trezorAddress !== ethAddress) { - // throw inconsistent state error - console.warn("Inconsistent state", trezorAddress, ethAddress); - - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'error', - title: 'Address validation error', - message: `Addresses are different. TREZOR: ${ trezorAddress } HDKey: ${ ethAddress }`, - cancelable: true, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch(start(device, discoveryProcess.network)) - } - } - ] - } - }); - return; - } - } else { - // handle TREZOR communication error - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'error', - title: 'Address validation error', - message: verifyAddress.payload.error, - cancelable: true, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch(start(device, discoveryProcess.network)) - } - } - ] - } - }); - return; - } - - const web3instance = getState().web3.find(w3 => w3.network === network); - if (!web3instance) return; - - const balance = await getBalanceAsync(web3instance.web3, ethAddress); - if (discoveryProcess.interrupted) return; - const nonce: number = await getNonceAsync(web3instance.web3, ethAddress); - if (discoveryProcess.interrupted) return; - - const addressIsEmpty = nonce < 1 && !balance.greaterThan(0); - - if (!addressIsEmpty || (addressIsEmpty && completed) || (addressIsEmpty && discoveryProcess.accountIndex === 0)) { - dispatch({ - type: ACCOUNT.CREATE, - device, - network, - index: discoveryProcess.accountIndex, - path, - address: ethAddress - }); - dispatch( - AccountsActions.setBalance(ethAddress, network, device.state || 'undefined', web3instance.web3.fromWei(balance.toString(), 'ether')) - ); - dispatch(AccountsActions.setNonce(ethAddress, network, device.state || 'undefined', nonce)); - - if (!completed) - dispatch( discoverAccount(device, discoveryProcess) ); - } - - if (addressIsEmpty) { - // release acquired sesssion - await TrezorConnect.getFeatures({ - device: { - path: device.path, - instance: device.instance, - state: device.state - }, - keepSession: false, - useEmptyPassphrase: !device.instance, - }); - if (discoveryProcess.interrupted) return; - - dispatch({ - type: DISCOVERY.COMPLETE, - device, - network - }); - } + dispatch({ + type: DISCOVERY.COMPLETE, + device, + network, + }); } -} +}; -export const restore = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const selected = getState().wallet.selectedDevice; +export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const selected = getState().wallet.selectedDevice; - if (selected && selected.connected && selected.features) { - const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.waitingForDevice); - if (discoveryProcess) { - dispatch( start(selected, discoveryProcess.network) ); - } + if (selected && selected.connected && selected.features) { + const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.waitingForDevice); + if (discoveryProcess) { + dispatch(start(selected, discoveryProcess.network)); } } -} +}; // TODO: rename method to something intuitive // there is no discovery process but it should be // this is possible race condition when "network" was changed in url but device was not authenticated yet // try to start discovery after CONNECT.AUTH_DEVICE action -export const check = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const selected = getState().wallet.selectedDevice; - if (!selected) return; - - const urlParams = getState().router.location.state; - if (urlParams.network) { - const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.network === urlParams.network); - if (!discoveryProcess) { - dispatch( start(selected, urlParams.network) ); - } +export const check = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const selected = getState().wallet.selectedDevice; + if (!selected) return; + + const urlParams = getState().router.location.state; + if (urlParams.network) { + const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.network === urlParams.network); + if (!discoveryProcess) { + dispatch(start(selected, urlParams.network)); } } -} +}; -export const stop = (device: TrezorDevice): Action => { - // TODO: release devices session - // corner case switch /eth to /etc (discovery start stop - should not be async) - return { - type: DISCOVERY.STOP, - device - } -} \ No newline at end of file +export const stop = (device: TrezorDevice): Action => ({ + type: DISCOVERY.STOP, + device, +}); diff --git a/src/js/actions/HistoryActions.js b/src/js/actions/HistoryActions.js index 0f324a80..e667c471 100644 --- a/src/js/actions/HistoryActions.js +++ b/src/js/actions/HistoryActions.js @@ -1,2 +1 @@ /* @flow */ -'use strict'; diff --git a/src/js/actions/LocalStorageActions.js b/src/js/actions/LocalStorageActions.js index b25ca328..b66a1b33 100644 --- a/src/js/actions/LocalStorageActions.js +++ b/src/js/actions/LocalStorageActions.js @@ -1,15 +1,17 @@ /* @flow */ -'use strict'; + import * as CONNECT from './constants/TrezorConnect'; import * as ACCOUNT from './constants/account'; import * as TOKEN from './constants/token'; import * as DISCOVERY from './constants/discovery'; import * as STORAGE from './constants/localStorage'; -import * as PENDING from '../actions/constants/pendingTx'; +import * as PENDING from './constants/pendingTx'; import { JSONRequest, httpRequest } from '../utils/networkUtils'; -import type { ThunkAction, AsyncAction, GetState, Dispatch, TrezorDevice } from '~/flowtype'; +import type { + ThunkAction, AsyncAction, GetState, Dispatch, TrezorDevice, +} from '~/flowtype'; import type { Config, Coin, TokensCollection } from '../reducers/LocalStorageReducer'; import AppConfigJSON from '~/data/appConfig.json'; @@ -28,24 +30,21 @@ export type StorageAction = { error: string, }; -export const loadData = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - // check if local storage is available - // let available: boolean = true; - // if (typeof window.localStorage === 'undefined') { - // available = false; - // } else { - // try { - // window.localStorage.setItem('ethereum_wallet', true); - // } catch (error) { - // available = false; - // } - // } - - dispatch( loadTokensFromJSON() ); - } -} +export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + // check if local storage is available + // let available: boolean = true; + // if (typeof window.localStorage === 'undefined') { + // available = false; + // } else { + // try { + // window.localStorage.setItem('ethereum_wallet', true); + // } catch (error) { + // available = false; + // } + // } + + dispatch(loadTokensFromJSON()); +}; // const parseConfig = (json: JSON): Config => { @@ -68,13 +67,13 @@ export const loadData = (): ThunkAction => { // if (json.config && json.hasOwnProperty('coins') && Array.isArray(json.coins)) { // json.coins.map(c => { // return { - + // } // }) // } else { // throw new Error(`Property "coins" is missing in appConfig.json`); // } - + // return { // coins: [], @@ -84,22 +83,21 @@ export const loadData = (): ThunkAction => { export function loadTokensFromJSON(): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - if (typeof window.localStorage === 'undefined') return; try { const config: Config = await httpRequest(AppConfigJSON, 'json'); const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json'); - window.addEventListener('storage', event => { - dispatch( update(event) ); - }) + window.addEventListener('storage', (event) => { + dispatch(update(event)); + }); // load tokens const tokens = await config.coins.reduce(async (promise: Promise, coin: Coin): Promise => { const collection: TokensCollection = await promise; const json = await httpRequest(coin.tokens, 'json'); - collection[ coin.network ] = json; + collection[coin.network] = json; return collection; }, Promise.resolve({})); @@ -107,62 +105,60 @@ export function loadTokensFromJSON(): AsyncAction { if (devices) { dispatch({ type: CONNECT.DEVICE_FROM_STORAGE, - payload: JSON.parse(devices) - }) + payload: JSON.parse(devices), + }); } const accounts: ?string = get('accounts'); if (accounts) { dispatch({ type: ACCOUNT.FROM_STORAGE, - payload: JSON.parse(accounts) - }) + payload: JSON.parse(accounts), + }); } const userTokens: ?string = get('tokens'); if (userTokens) { dispatch({ type: TOKEN.FROM_STORAGE, - payload: JSON.parse(userTokens) - }) + payload: JSON.parse(userTokens), + }); } const pending: ?string = get('pending'); if (pending) { dispatch({ type: PENDING.FROM_STORAGE, - payload: JSON.parse(pending) - }) + payload: JSON.parse(pending), + }); } const discovery: ?string = get('discovery'); if (discovery) { dispatch({ type: DISCOVERY.FROM_STORAGE, - payload: JSON.parse(discovery) - }) + payload: JSON.parse(discovery), + }); } - - + + dispatch({ type: STORAGE.READY, config, tokens, - ERC20Abi - }) - - } catch(error) { + ERC20Abi, + }); + } catch (error) { dispatch({ type: STORAGE.ERROR, - error - }) + error, + }); } - } + }; } export function update(event: StorageEvent): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - if (!event.newValue) return; if (event.key === 'devices') { @@ -183,46 +179,44 @@ export function update(event: StorageEvent): AsyncAction { if (event.key === 'accounts') { dispatch({ type: ACCOUNT.FROM_STORAGE, - payload: JSON.parse(event.newValue) + payload: JSON.parse(event.newValue), }); } if (event.key === 'tokens') { dispatch({ type: TOKEN.FROM_STORAGE, - payload: JSON.parse(event.newValue) + payload: JSON.parse(event.newValue), }); } if (event.key === 'pending') { dispatch({ type: PENDING.FROM_STORAGE, - payload: JSON.parse(event.newValue) + payload: JSON.parse(event.newValue), }); } if (event.key === 'discovery') { dispatch({ type: DISCOVERY.FROM_STORAGE, - payload: JSON.parse(event.newValue) + payload: JSON.parse(event.newValue), }); } - } + }; } -export const save = (key: string, value: string): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - if (typeof window.localStorage !== 'undefined') { - try { - window.localStorage.setItem(key, value); - } catch (error) { - // available = false; - console.error("Local Storage ERROR: " + error) - } +export const save = (key: string, value: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + if (typeof window.localStorage !== 'undefined') { + try { + window.localStorage.setItem(key, value); + } catch (error) { + // available = false; + console.error(`Local Storage ERROR: ${error}`); } } -} +}; export const get = (key: string): ?string => { try { @@ -231,4 +225,4 @@ export const get = (key: string): ?string => { // available = false; return null; } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/actions/LogActions.js b/src/js/actions/LogActions.js index d645fe9d..be9b3b13 100644 --- a/src/js/actions/LogActions.js +++ b/src/js/actions/LogActions.js @@ -1,9 +1,11 @@ /* @flow */ -'use strict'; + import * as LOG from './constants/log'; -import type { Action, ThunkAction, GetState, Dispatch } from '~/flowtype'; +import type { + Action, ThunkAction, GetState, Dispatch, +} from '~/flowtype'; import type { LogEntry } from '../reducers/LogReducer'; export type LogAction = { @@ -15,31 +17,26 @@ export type LogAction = { payload: LogEntry }; -export const toggle = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - if (!getState().log.opened) { - window.scrollTo(0, 0); +export const toggle = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + if (!getState().log.opened) { + window.scrollTo(0, 0); - dispatch({ - type: LOG.OPEN - }); - } else { - dispatch({ - type: LOG.CLOSE - }); - } + dispatch({ + type: LOG.OPEN, + }); + } else { + dispatch({ + type: LOG.CLOSE, + }); } -} +}; // export const add = (type: string, message: string): Action => { -export const add = (type: string, message: any): Action => { - return { - type: LOG.ADD, - payload: { - time: new Date().getTime(), - type, - message - } - } -} +export const add = (type: string, message: any): Action => ({ + type: LOG.ADD, + payload: { + time: new Date().getTime(), + type, + message, + }, +}); diff --git a/src/js/actions/ModalActions.js b/src/js/actions/ModalActions.js index 137c1465..ba73a0dd 100644 --- a/src/js/actions/ModalActions.js +++ b/src/js/actions/ModalActions.js @@ -1,13 +1,15 @@ /* @flow */ -'use strict'; + import TrezorConnect, { UI, UI_EVENT } from 'trezor-connect'; +import type { Device } from 'trezor-connect'; import * as MODAL from './constants/modal'; import * as CONNECT from './constants/TrezorConnect'; -import type { ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice } from '~/flowtype'; +import type { + ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice, +} from '~/flowtype'; import type { State } from '../reducers/ModalReducer'; -import type { Device } from 'trezor-connect'; export type ModalAction = { type: typeof MODAL.CLOSE @@ -19,25 +21,23 @@ export type ModalAction = { export const onPinSubmit = (value: string): Action => { TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value }); return { - type: MODAL.CLOSE - } -} - -export const onPassphraseSubmit = (passphrase: string): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const resp = await TrezorConnect.uiResponse({ - type: UI.RECEIVE_PASSPHRASE, - payload: { - value: passphrase, - save: true - } - }); + type: MODAL.CLOSE, + }; +}; - dispatch({ - type: MODAL.CLOSE - }); - } -} +export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const resp = await TrezorConnect.uiResponse({ + type: UI.RECEIVE_PASSPHRASE, + payload: { + value: passphrase, + save: true, + }, + }); + + dispatch({ + type: MODAL.CLOSE, + }); +}; // export const askForRemember = (device: TrezorDevice): Action => { // return { @@ -46,88 +46,72 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => { // } // } -export const onRememberDevice = (device: TrezorDevice): Action => { - return { - type: CONNECT.REMEMBER, - device - } -} +export const onRememberDevice = (device: TrezorDevice): Action => ({ + type: CONNECT.REMEMBER, + device, +}); -export const onForgetDevice = (device: TrezorDevice): Action => { - return { - type: CONNECT.FORGET, - device, - } -} +export const onForgetDevice = (device: TrezorDevice): Action => ({ + type: CONNECT.FORGET, + device, +}); -export const onForgetSingleDevice = (device: TrezorDevice): Action => { - return { - type: CONNECT.FORGET_SINGLE, - device, - } -} +export const onForgetSingleDevice = (device: TrezorDevice): Action => ({ + type: CONNECT.FORGET_SINGLE, + device, +}); -export const onCancel = (): Action => { - return { - type: MODAL.CLOSE - } -} +export const onCancel = (): Action => ({ + type: MODAL.CLOSE, +}); + +export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + dispatch(onCancel()); -export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { + dispatch({ + type: CONNECT.DUPLICATE, + device, + }); +}; - dispatch( onCancel() ); +export const onRememberRequest = (prevState: State): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const state: State = getState().modal; + // handle case where forget modal is already opened + // TODO: 2 modals at once (two devices disconnected in the same time) + if (prevState.opened && prevState.windowType === CONNECT.REMEMBER_REQUEST) { + // forget current (new) + if (state.opened) { + dispatch({ + type: CONNECT.FORGET, + device: state.device, + }); + } + // forget previous (old) dispatch({ - type: CONNECT.DUPLICATE, - device + type: CONNECT.FORGET, + device: prevState.device, }); } -} - -export const onRememberRequest = (prevState: State): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const state: State = getState().modal; - // handle case where forget modal is already opened - // TODO: 2 modals at once (two devices disconnected in the same time) - if (prevState.opened && prevState.windowType === CONNECT.REMEMBER_REQUEST) { - // forget current (new) - if (state.opened) { - dispatch({ - type: CONNECT.FORGET, - device: state.device - }); - } - - // forget previous (old) +}; + +export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + // interrupt process of remembering device (force forget) + // TODO: the same for disconnect more than 1 device at once + const { modal } = getState(); + if (modal.opened && modal.windowType === CONNECT.REMEMBER_REQUEST) { + if (device.features && modal.device && modal.device.features && modal.device.features.device_id === device.features.device_id) { + dispatch({ + type: MODAL.CLOSE, + }); + } else { dispatch({ type: CONNECT.FORGET, - device: prevState.device + device: modal.device, }); } } -} - -export const onDeviceConnect = (device: Device): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - // interrupt process of remembering device (force forget) - // TODO: the same for disconnect more than 1 device at once - const { modal } = getState(); - if (modal.opened && modal.windowType === CONNECT.REMEMBER_REQUEST) { - if (device.features && modal.device && modal.device.features && modal.device.features.device_id === device.features.device_id) { - dispatch({ - type: MODAL.CLOSE, - }); - } else { - dispatch({ - type: CONNECT.FORGET, - device: modal.device - }); - } - } - - } -} +}; export default { onPinSubmit, @@ -137,5 +121,5 @@ export default { onForgetDevice, onForgetSingleDevice, onCancel, - onDuplicateDevice -} \ No newline at end of file + onDuplicateDevice, +}; \ No newline at end of file diff --git a/src/js/actions/NotificationActions.js b/src/js/actions/NotificationActions.js index e199cd14..ae6c9557 100644 --- a/src/js/actions/NotificationActions.js +++ b/src/js/actions/NotificationActions.js @@ -1,9 +1,11 @@ /* @flow */ -'use strict'; + import * as NOTIFICATION from './constants/notification'; -import type { Action, AsyncAction, GetState, Dispatch, RouterLocationState } from '~/flowtype'; +import type { + Action, AsyncAction, GetState, Dispatch, RouterLocationState, +} from '~/flowtype'; import type { CallbackAction } from '../reducers/NotificationReducer'; export type NotificationAction = { @@ -23,31 +25,27 @@ export type NotificationAction = { } } -export const close = (payload: any = {}): Action => { - return { - type: NOTIFICATION.CLOSE, - payload - } -} +export const close = (payload: any = {}): Action => ({ + type: NOTIFICATION.CLOSE, + payload, +}); // called from RouterService -export const clear = (currentParams: RouterLocationState, requestedParams: RouterLocationState): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - // if route has been changed from device view into something else (like other device, settings...) - // try to remove all Notifications which are linked to previous device (they are not cancelable by user) - if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) { - const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string'); - entries.forEach(entry => { - if (typeof entry.devicePath === 'string') { - dispatch({ - type: NOTIFICATION.CLOSE, - payload: { - devicePath: entry.devicePath - } - }) - } - }); - } +export const clear = (currentParams: RouterLocationState, requestedParams: RouterLocationState): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + // if route has been changed from device view into something else (like other device, settings...) + // try to remove all Notifications which are linked to previous device (they are not cancelable by user) + if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) { + const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string'); + entries.forEach((entry) => { + if (typeof entry.devicePath === 'string') { + dispatch({ + type: NOTIFICATION.CLOSE, + payload: { + devicePath: entry.devicePath, + }, + }); + } + }); } -} +}; diff --git a/src/js/actions/PendingTxActions.js b/src/js/actions/PendingTxActions.js index c0f38520..ece7c37c 100644 --- a/src/js/actions/PendingTxActions.js +++ b/src/js/actions/PendingTxActions.js @@ -1,8 +1,8 @@ /* @flow */ -'use strict'; + import * as PENDING from './constants/pendingTx'; -import type { State, PendingTx } from '../reducers/PendingTxReducer' +import type { State, PendingTx } from '../reducers/PendingTxReducer'; export type PendingTxAction = { type: typeof PENDING.FROM_STORAGE, diff --git a/src/js/actions/ReceiveActions.js b/src/js/actions/ReceiveActions.js index 11a38058..60a8a087 100644 --- a/src/js/actions/ReceiveActions.js +++ b/src/js/actions/ReceiveActions.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import TrezorConnect from 'trezor-connect'; import * as RECEIVE from './constants/receive'; @@ -8,7 +8,9 @@ import * as NOTIFICATION from './constants/notification'; import { initialState } from '../reducers/ReceiveReducer'; import type { State } from '../reducers/ReceiveReducer'; -import type { TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch } from '~/flowtype'; +import type { + TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch, +} from '~/flowtype'; export type ReceiveAction = { type: typeof RECEIVE.INIT, @@ -26,90 +28,80 @@ export type ReceiveAction = { type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS } -export const init = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - const state: State = { - ...initialState, - }; +export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const state: State = { + ...initialState, + }; - dispatch({ - type: RECEIVE.INIT, - state: state - }); - } -} + dispatch({ + type: RECEIVE.INIT, + state, + }); +}; -export const dispose = (): Action => { - return { - type: RECEIVE.DISPOSE - } -} +export const dispose = (): Action => ({ + type: RECEIVE.DISPOSE, +}); -export const showUnverifiedAddress = (): Action => { - return { - type: RECEIVE.SHOW_UNVERIFIED_ADDRESS - } -} +export const showUnverifiedAddress = (): Action => ({ + type: RECEIVE.SHOW_UNVERIFIED_ADDRESS, +}); //export const showAddress = (address_n: string): AsyncAction => { -export const showAddress = (address_n: Array): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { +export const showAddress = (address_n: Array): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const selected = getState().wallet.selectedDevice; + if (!selected) return; + + if (selected && (!selected.connected || !selected.available)) { + dispatch({ + type: RECEIVE.REQUEST_UNVERIFIED, + device: selected, + }); + return; + } - const selected = getState().wallet.selectedDevice; - if (!selected) return; + const response = await TrezorConnect.ethereumGetAddress({ + device: { + path: selected.path, + instance: selected.instance, + state: selected.state, + }, + path: address_n, + useEmptyPassphrase: !selected.instance, + }); - if (selected && (!selected.connected || !selected.available)) { - dispatch({ - type: RECEIVE.REQUEST_UNVERIFIED, - device: selected - }); - return; - } + if (response && response.success) { + dispatch({ + type: RECEIVE.SHOW_ADDRESS, + }); + } else { + dispatch({ + type: RECEIVE.HIDE_ADDRESS, + }); - const response = await TrezorConnect.ethereumGetAddress({ - device: { - path: selected.path, - instance: selected.instance, - state: selected.state + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Verifying address error', + message: response.payload.error, + cancelable: true, + actions: [ + { + label: 'Try again', + callback: () => { + dispatch(showAddress(address_n)); + }, + }, + ], }, - path: address_n, - useEmptyPassphrase: !selected.instance, }); - - if (response && response.success) { - dispatch({ - type: RECEIVE.SHOW_ADDRESS - }) - } else { - dispatch({ - type: RECEIVE.HIDE_ADDRESS - }) - - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'error', - title: 'Verifying address error', - message: response.payload.error, - cancelable: true, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch(showAddress(address_n)) - } - } - ] - } - }) - } } -} +}; export default { init, dispose, showAddress, - showUnverifiedAddress -} \ No newline at end of file + showUnverifiedAddress, +}; \ No newline at end of file diff --git a/src/js/actions/SelectedAccountActions.js b/src/js/actions/SelectedAccountActions.js index 44ca0b3e..9d18709a 100644 --- a/src/js/actions/SelectedAccountActions.js +++ b/src/js/actions/SelectedAccountActions.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import { LOCATION_CHANGE } from 'react-router-redux'; import * as ACCOUNT from './constants/account'; @@ -18,8 +18,8 @@ import type { TrezorDevice, AsyncAction, ThunkAction, - Action, - GetState, + Action, + GetState, Dispatch, State, } from '~/flowtype'; @@ -32,110 +32,101 @@ export type SelectedAccountAction = { payload: $ElementType }; -export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - - const locationChange: boolean = action.type === LOCATION_CHANGE; - const state: State = getState(); - const location = state.router.location; - const prevLocation = prevState.router.location; +export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const locationChange: boolean = action.type === LOCATION_CHANGE; + const state: State = getState(); + const location = state.router.location; + const prevLocation = prevState.router.location; - let needUpdate: boolean = false; + const needUpdate: boolean = false; - // reset form to default - if (action.type === SEND.TX_COMPLETE) { - // dispatch( SendFormActions.init() ); - // linear action - // SessionStorageActions.clear(location.pathname); - } + // reset form to default + if (action.type === SEND.TX_COMPLETE) { + // dispatch( SendFormActions.init() ); + // linear action + // SessionStorageActions.clear(location.pathname); + } - // handle devices state change (from trezor-connect events or location change) - if (locationChange + // handle devices state change (from trezor-connect events or location change) + if (locationChange || prevState.accounts !== state.accounts || prevState.discovery !== state.discovery || prevState.tokens !== state.tokens || prevState.pending !== state.pending || prevState.web3 !== state.web3) { + if (locationChange) { + // dispose current account view + dispatch(dispose()); + } + const account = stateUtils.getSelectedAccount(state); + const network = stateUtils.getSelectedNetwork(state); + const discovery = stateUtils.getDiscoveryProcess(state); + const tokens = stateUtils.getAccountTokens(state, account); + const pending = stateUtils.getAccountPendingTx(state.pending, account); + const web3 = stateUtils.getWeb3(state); + + const payload: $ElementType = { + // location: location.pathname, + account, + network, + discovery, + tokens, + pending, + web3, + }; - if (locationChange) { - // dispose current account view - dispatch( dispose() ); - } - - const account = stateUtils.getSelectedAccount(state); - const network = stateUtils.getSelectedNetwork(state); - const discovery = stateUtils.getDiscoveryProcess(state); - const tokens = stateUtils.getAccountTokens(state, account); - const pending = stateUtils.getAccountPendingTx(state.pending, account); - const web3 = stateUtils.getWeb3(state); - - const payload: $ElementType = { - // location: location.pathname, - account, - network, - discovery, - tokens, - pending, - web3 - } - - let needUpdate: boolean = false; - Object.keys(payload).forEach((key) => { - if (Array.isArray(payload[key])) { - if (Array.isArray(state.selectedAccount[key]) && payload[key].length !== state.selectedAccount[key].length) { - needUpdate = true; - } - } else { - if (payload[key] !== state.selectedAccount[key]) { - needUpdate = true; - } + let needUpdate: boolean = false; + Object.keys(payload).forEach((key) => { + if (Array.isArray(payload[key])) { + if (Array.isArray(state.selectedAccount[key]) && payload[key].length !== state.selectedAccount[key].length) { + needUpdate = true; } - }) + } else if (payload[key] !== state.selectedAccount[key]) { + needUpdate = true; + } + }); - if (needUpdate) { - dispatch({ - type: ACCOUNT.UPDATE_SELECTED_ACCOUNT, - payload, - }); + if (needUpdate) { + dispatch({ + type: ACCOUNT.UPDATE_SELECTED_ACCOUNT, + payload, + }); - // initialize SendFormReducer - if (location.state.send && getState().sendForm.currency === "") { - dispatch( SendFormActions.init() ); - } + // initialize SendFormReducer + if (location.state.send && getState().sendForm.currency === '') { + dispatch(SendFormActions.init()); + } - if (location.state.send) { - const rejectedTxs = pending.filter(tx => tx.rejected); - rejectedTxs.forEach(tx => { - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: "warning", - title: "Pending transaction rejected", - message: `Transaction with id: ${ tx.id } not found.`, - cancelable: true, - actions: [ - { - label: 'OK', - callback: () => { - dispatch({ - type: PENDING.TX_RESOLVED, - tx, - }); - } - } - ] - } - }) + if (location.state.send) { + const rejectedTxs = pending.filter(tx => tx.rejected); + rejectedTxs.forEach((tx) => { + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'warning', + title: 'Pending transaction rejected', + message: `Transaction with id: ${tx.id} not found.`, + cancelable: true, + actions: [ + { + label: 'OK', + callback: () => { + dispatch({ + type: PENDING.TX_RESOLVED, + tx, + }); + }, + }, + ], + }, }); - } + }); } } } -} +}; -export const dispose = (): Action => { - return { - type: ACCOUNT.DISPOSE - } -} \ No newline at end of file +export const dispose = (): Action => ({ + type: ACCOUNT.DISPOSE, +}); \ No newline at end of file diff --git a/src/js/actions/SendFormActions.js b/src/js/actions/SendFormActions.js index aca7959f..e1e6a5e7 100644 --- a/src/js/actions/SendFormActions.js +++ b/src/js/actions/SendFormActions.js @@ -1,20 +1,17 @@ /* @flow */ -'use strict'; -import * as SEND from './constants/send'; -import * as NOTIFICATION from './constants/notification'; - -import * as SessionStorageActions from './SessionStorageActions'; - -import { estimateGas, getGasPrice, pushTx } from './Web3Actions'; import EthereumjsUtil from 'ethereumjs-util'; import EthereumjsUnits from 'ethereumjs-units'; import EthereumjsTx from 'ethereumjs-tx'; import TrezorConnect from 'trezor-connect'; -import { strip } from '../utils/ethUtils'; import { push } from 'react-router-redux'; import BigNumber from 'bignumber.js'; +import { strip } from '../utils/ethUtils'; +import { estimateGas, getGasPrice, pushTx } from './Web3Actions'; +import * as SessionStorageActions from './SessionStorageActions'; +import * as NOTIFICATION from './constants/notification'; +import * as SEND from './constants/send'; import { initialState } from '../reducers/SendFormReducer'; import { findAccount } from '../reducers/AccountsReducer'; @@ -22,7 +19,7 @@ import { findToken } from '../reducers/TokensReducer'; import { findDevice } from '../reducers/utils'; import * as stateUtils from '../reducers/utils'; -import type { +import type { PendingTx, Dispatch, GetState, @@ -30,7 +27,7 @@ import type { ThunkAction, AsyncAction, RouterLocationState, - TrezorDevice + TrezorDevice, } from '~/flowtype'; import type { State as AccountState } from '../reducers/SelectedAccountReducer'; import type { Web3Instance } from '../reducers/Web3Reducer'; @@ -124,19 +121,19 @@ const numberRegExp: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9] export const calculateFee = (gasPrice: string, gasLimit: string): string => { try { - return EthereumjsUnits.convert( new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether'); + return EthereumjsUnits.convert(new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether'); } catch (error) { return '0'; } -} +}; export const calculateTotal = (amount: string, gasPrice: string, gasLimit: string): string => { try { - return new BigNumber(amount).plus( calculateFee(gasPrice, gasLimit) ).toString(10); + return new BigNumber(amount).plus(calculateFee(gasPrice, gasLimit)).toString(10); } catch (error) { return '0'; } -} +}; export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimit: string): string => { try { @@ -148,8 +145,7 @@ export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimi } catch (error) { return '0'; } - -} +}; export const calculate = (prevProps: Props, props: Props) => { const { @@ -166,14 +162,13 @@ export const calculate = (prevProps: Props, props: Props) => { // account balance // token balance // gasLimit, gasPrice changed - - // const shouldRecalculateAmount = + + // const shouldRecalculateAmount = // (prevProps.selectedAccount.account !== account) // || (prevProps.) - - - if (state.setMax) { + + if (state.setMax) { const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, state.currency, isToken); if (isToken) { @@ -192,160 +187,152 @@ export const calculate = (prevProps: Props, props: Props) => { state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit); if (state.selectedFeeLevel.value === 'Custom') { - state.selectedFeeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.networkSymbol }`; + state.selectedFeeLevel.label = `${calculateFee(state.gasPrice, state.gasLimit)} ${state.networkSymbol}`; state.selectedFeeLevel.gasPrice = state.gasPrice; } -} +}; export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string, selected?: FeeLevel): Array => { - const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice + const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice; const quarter: BigNumber = price.dividedBy(4); const high: string = price.plus(quarter.times(2)).toString(10); const low: string = price.minus(quarter.times(2)).toString(10); - + const customLevel: FeeLevel = selected && selected.value === 'Custom' ? { value: 'Custom', - gasPrice: selected.gasPrice, + gasPrice: selected.gasPrice, // label: `${ calculateFee(gasPrice, gasLimit) } ${ symbol }` - label: `${ calculateFee(selected.gasPrice, gasLimit) } ${ symbol }` + label: `${calculateFee(selected.gasPrice, gasLimit)} ${symbol}`, } : { value: 'Custom', gasPrice: low, - label: '' - } + label: '', + }; return [ - { + { value: 'High', gasPrice: high, - label: `${ calculateFee(high, gasLimit) } ${ symbol }` + label: `${calculateFee(high, gasLimit)} ${symbol}`, }, - { + { value: 'Normal', gasPrice: gasPrice.toString(), - label: `${ calculateFee(price.toString(10), gasLimit) } ${ symbol }` + label: `${calculateFee(price.toString(10), gasLimit)} ${symbol}`, }, - { + { value: 'Low', gasPrice: low, - label: `${ calculateFee(low, gasLimit) } ${ symbol }` + label: `${calculateFee(low, gasLimit)} ${symbol}`, }, - customLevel - ] -} + customLevel, + ]; +}; // initialize component -export const init = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - const { - account, - network, - web3 - } = getState().selectedAccount; - - if (!account || !network || !web3) return; - - const stateFromStorage = SessionStorageActions.load( getState().router.location.pathname ); - if (stateFromStorage) { - dispatch({ - type: SEND.INIT, - state: stateFromStorage - }); - return; - } - - // TODO: check if there are some unfinished tx in localStorage - - const gasPrice: BigNumber = new BigNumber( EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei') ) || new BigNumber(network.defaultGasPrice); - const gasLimit: string = network.defaultGasLimit.toString(); - const feeLevels: Array = getFeeLevels(network.symbol, gasPrice, gasLimit); - - // TODO: get nonce - // TODO: LOAD DATA FROM SESSION STORAGE +export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const { + account, + network, + web3, + } = getState().selectedAccount; - const state: State = { - ...initialState, - networkName: network.network, - networkSymbol: network.symbol, - currency: network.symbol, - feeLevels, - selectedFeeLevel: feeLevels.find(f => f.value === 'Normal'), - recommendedGasPrice: gasPrice.toString(), - gasLimit, - gasPrice: gasPrice.toString(), - }; + if (!account || !network || !web3) return; + const stateFromStorage = SessionStorageActions.load(getState().router.location.pathname); + if (stateFromStorage) { dispatch({ type: SEND.INIT, - state + state: stateFromStorage, }); + return; } -} -export const toggleAdvanced = (address: string): Action => { - return { - type: SEND.TOGGLE_ADVANCED - } -} + // TODO: check if there are some unfinished tx in localStorage + + const gasPrice: BigNumber = new BigNumber(EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei')) || new BigNumber(network.defaultGasPrice); + const gasLimit: string = network.defaultGasLimit.toString(); + const feeLevels: Array = getFeeLevels(network.symbol, gasPrice, gasLimit); + + // TODO: get nonce + // TODO: LOAD DATA FROM SESSION STORAGE + + const state: State = { + ...initialState, + networkName: network.network, + networkSymbol: network.symbol, + currency: network.symbol, + feeLevels, + selectedFeeLevel: feeLevels.find(f => f.value === 'Normal'), + recommendedGasPrice: gasPrice.toString(), + gasLimit, + gasPrice: gasPrice.toString(), + }; + + dispatch({ + type: SEND.INIT, + state, + }); +}; +export const toggleAdvanced = (address: string): Action => ({ + type: SEND.TOGGLE_ADVANCED, +}); -const addressValidation = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const { - account, - network, - tokens, - } = getState().selectedAccount; - if (!account || !network) return; - - const state: State = getState().sendForm; - const infos = { ...state.infos }; - const warnings = { ...state.warnings }; - - - if (state.untouched || !state.touched.address) return; - - const savedAccounts = getState().accounts.filter(a => a.address.toLowerCase() === state.address.toLowerCase()); - if (savedAccounts.length > 0) { - // check if found account belongs to this network - // corner-case: when same derivation path is used on different networks - const currentNetworkAccount = savedAccounts.find(a => a.network === network.network); - if (currentNetworkAccount) { - const device: ?TrezorDevice = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState); - if (device) { - infos.address = `${ device.instanceLabel } Account #${ (currentNetworkAccount.index + 1) }`; - } - } else { - const otherNetworkAccount = savedAccounts[0]; - const device: ?TrezorDevice = findDevice(getState().devices, otherNetworkAccount.deviceID, otherNetworkAccount.deviceState); - const coins = getState().localStorage.config.coins; - const otherNetwork: ?Coin = coins.find(c => c.network === otherNetworkAccount.network) - if (device && otherNetwork) { - warnings.address = `Looks like it's ${ device.instanceLabel } Account #${ (otherNetworkAccount.index + 1) } address of ${ otherNetwork.name } network`; - } + +const addressValidation = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const { + account, + network, + tokens, + } = getState().selectedAccount; + if (!account || !network) return; + + const state: State = getState().sendForm; + const infos = { ...state.infos }; + const warnings = { ...state.warnings }; + + + if (state.untouched || !state.touched.address) return; + + const savedAccounts = getState().accounts.filter(a => a.address.toLowerCase() === state.address.toLowerCase()); + if (savedAccounts.length > 0) { + // check if found account belongs to this network + // corner-case: when same derivation path is used on different networks + const currentNetworkAccount = savedAccounts.find(a => a.network === network.network); + if (currentNetworkAccount) { + const device: ?TrezorDevice = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState); + if (device) { + infos.address = `${device.instanceLabel} Account #${(currentNetworkAccount.index + 1)}`; } } else { - delete warnings.address; - delete infos.address; - } - - dispatch({ - type: SEND.ADDRESS_VALIDATION, - state: { - ...state, - infos, - warnings + const otherNetworkAccount = savedAccounts[0]; + const device: ?TrezorDevice = findDevice(getState().devices, otherNetworkAccount.deviceID, otherNetworkAccount.deviceState); + const coins = getState().localStorage.config.coins; + const otherNetwork: ?Coin = coins.find(c => c.network === otherNetworkAccount.network); + if (device && otherNetwork) { + warnings.address = `Looks like it's ${device.instanceLabel} Account #${(otherNetworkAccount.index + 1)} address of ${otherNetwork.name} network`; } - }) + } + } else { + delete warnings.address; + delete infos.address; } -} + + dispatch({ + type: SEND.ADDRESS_VALIDATION, + state: { + ...state, + infos, + warnings, + }, + }); +}; export const validation = (props: Props): void => { - const { account, network, @@ -388,7 +375,6 @@ export const validation = (props: Props): void => { } else if (state.amount.length > 0 && !state.amount.match(numberRegExp)) { errors.amount = 'Amount is not a number'; } else { - let decimalRegExp: RegExp; const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, state.currency, state.currency !== state.networkSymbol); @@ -397,34 +383,33 @@ export const validation = (props: Props): void => { if (token) { if (parseInt(token.decimals) > 0) { //decimalRegExp = new RegExp('^(0|0\\.([0-9]{0,' + token.decimals + '})?|[1-9]+\\.?([0-9]{0,' + token.decimals + '})?|\\.[0-9]{1,' + token.decimals + '})$'); - decimalRegExp = new RegExp('^(0|0\\.([0-9]{0,' + token.decimals + '})?|[1-9][0-9]*\\.?([0-9]{0,' + token.decimals + '})?|\\.[0-9]{1,' + token.decimals + '})$'); + decimalRegExp = new RegExp(`^(0|0\\.([0-9]{0,${token.decimals}})?|[1-9][0-9]*\\.?([0-9]{0,${token.decimals}})?|\\.[0-9]{1,${token.decimals}})$`); } else { // decimalRegExp = new RegExp('^(0|0\\.?|[1-9]+\\.?)$'); decimalRegExp = new RegExp('^[0-9]+$'); } if (!state.amount.match(decimalRegExp)) { - errors.amount = `Maximum ${ token.decimals } decimals allowed`; + errors.amount = `Maximum ${token.decimals} decimals allowed`; } else if (new BigNumber(state.total).greaterThan(account.balance)) { - errors.amount = `Not enough ${ state.networkSymbol } to cover transaction fee`; - } else if (new BigNumber(state.amount).greaterThan( new BigNumber(token.balance).minus(pendingAmount) )) { + errors.amount = `Not enough ${state.networkSymbol} to cover transaction fee`; + } else if (new BigNumber(state.amount).greaterThan(new BigNumber(token.balance).minus(pendingAmount))) { errors.amount = 'Not enough funds'; } else if (new BigNumber(state.amount).lessThanOrEqualTo('0')) { errors.amount = 'Amount is too low'; } } - } else { decimalRegExp = new RegExp('^(0|0\\.([0-9]{0,18})?|[1-9][0-9]*\\.?([0-9]{0,18})?|\\.[0-9]{0,18})$'); if (!state.amount.match(decimalRegExp)) { - errors.amount = `Maximum 18 decimals allowed`; - } else if (new BigNumber(state.total).greaterThan( new BigNumber(account.balance).minus(pendingAmount) )) { + errors.amount = 'Maximum 18 decimals allowed'; + } else if (new BigNumber(state.total).greaterThan(new BigNumber(account.balance).minus(pendingAmount))) { errors.amount = 'Not enough funds'; } } } } - + // valid gas limit if (state.touched.gasLimit) { if (state.gasLimit.length < 1) { @@ -435,7 +420,7 @@ export const validation = (props: Props): void => { const gl: BigNumber = new BigNumber(state.gasLimit); if (gl.lessThan(1)) { errors.gasLimit = 'Gas limit is too low'; - } else if (gl.lessThan( state.currency !== state.networkSymbol ? network.defaultGasLimitTokens : network.defaultGasLimit )) { + } else if (gl.lessThan(state.currency !== state.networkSymbol ? network.defaultGasLimitTokens : network.defaultGasLimit)) { warnings.gasLimit = 'Gas limit is below recommended'; } } @@ -487,483 +472,446 @@ export const validation = (props: Props): void => { state.errors = errors; state.warnings = warnings; state.infos = infos; +}; -} - - - - -export const onAddressChange = (address: string): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const state: State = getState().sendForm; - const touched = { ...state.touched }; - touched.address = true; - - dispatch({ - type: SEND.ADDRESS_CHANGE, - state: { - ...state, - untouched: false, - touched, - address - } - }); - - dispatch( addressValidation() ); - } -} -export const onAmountChange = (amount: string): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const state = getState().sendForm; - const touched = { ...state.touched }; - touched.amount = true; +export const onAddressChange = (address: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const state: State = getState().sendForm; + const touched = { ...state.touched }; + touched.address = true; - dispatch({ - type: SEND.AMOUNT_CHANGE, - state: { - ...state, - untouched: false, - touched, - setMax: false, - amount, - } - }); - } -} + dispatch({ + type: SEND.ADDRESS_CHANGE, + state: { + ...state, + untouched: false, + touched, + address, + }, + }); -export const onCurrencyChange = (currency: { value: string, label: string }): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const { - account, - network - } = getState().selectedAccount; - if (!account || !network) return; - - const currentState: State = getState().sendForm; - const isToken: boolean = currency.value !== currentState.networkSymbol; - const gasLimit: string = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString(); - - const feeLevels: Array = getFeeLevels(network.symbol, currentState.recommendedGasPrice, gasLimit, currentState.selectedFeeLevel); - const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value); - if (!selectedFeeLevel) return; - - const state: State = { - ...currentState, - currency: currency.value, - // amount, - // total, - feeLevels, - selectedFeeLevel, - gasLimit, - }; + dispatch(addressValidation()); +}; - dispatch({ - type: SEND.CURRENCY_CHANGE, - state - }); - } -} +export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const state = getState().sendForm; + const touched = { ...state.touched }; + touched.amount = true; -export const onSetMax = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const state = getState().sendForm; - const touched = { ...state.touched }; - touched.amount = true; + dispatch({ + type: SEND.AMOUNT_CHANGE, + state: { + ...state, + untouched: false, + touched, + setMax: false, + amount, + }, + }); +}; - dispatch({ - type: SEND.SET_MAX, - state: { - ...state, - untouched: false, - touched, - setMax: !state.setMax, - } - }); - } -} +export const onCurrencyChange = (currency: { value: string, label: string }): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const { + account, + network, + } = getState().selectedAccount; + if (!account || !network) return; -export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const { - network - } = getState().selectedAccount; - if (!network) return; + const currentState: State = getState().sendForm; + const isToken: boolean = currency.value !== currentState.networkSymbol; + const gasLimit: string = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString(); + + const feeLevels: Array = getFeeLevels(network.symbol, currentState.recommendedGasPrice, gasLimit, currentState.selectedFeeLevel); + const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value); + if (!selectedFeeLevel) return; + + const state: State = { + ...currentState, + currency: currency.value, + // amount, + // total, + feeLevels, + selectedFeeLevel, + gasLimit, + }; + + dispatch({ + type: SEND.CURRENCY_CHANGE, + state, + }); +}; - const currentState: State = getState().sendForm; - const isToken: boolean = currentState.currency !== currentState.networkSymbol; +export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const state = getState().sendForm; + const touched = { ...state.touched }; + touched.amount = true; - const state: State = { - ...currentState, + dispatch({ + type: SEND.SET_MAX, + state: { + ...state, untouched: false, - selectedFeeLevel: feeLevel, - }; + touched, + setMax: !state.setMax, + }, + }); +}; - if (feeLevel.value === 'Custom') { - state.advanced = true; - feeLevel.gasPrice = state.gasPrice; - feeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.networkSymbol }`; +export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const { + network, + } = getState().selectedAccount; + if (!network) return; + + const currentState: State = getState().sendForm; + const isToken: boolean = currentState.currency !== currentState.networkSymbol; + + const state: State = { + ...currentState, + untouched: false, + selectedFeeLevel: feeLevel, + }; + + if (feeLevel.value === 'Custom') { + state.advanced = true; + feeLevel.gasPrice = state.gasPrice; + feeLevel.label = `${calculateFee(state.gasPrice, state.gasLimit)} ${state.networkSymbol}`; + } else { + const customLevel: ?FeeLevel = state.feeLevels.find(f => f.value === 'Custom'); + if (customLevel) customLevel.label = ''; + state.gasPrice = feeLevel.gasPrice; + if (isToken) { + state.gasLimit = network.defaultGasLimitTokens.toString(); } else { - const customLevel: ?FeeLevel = state.feeLevels.find(f => f.value === 'Custom'); - if (customLevel) - customLevel.label = ''; - state.gasPrice = feeLevel.gasPrice; - if (isToken) { - state.gasLimit = network.defaultGasLimitTokens.toString() - } else { - state.gasLimit = state.data.length > 0 ? state.gasLimit : network.defaultGasLimit.toString(); - } + state.gasLimit = state.data.length > 0 ? state.gasLimit : network.defaultGasLimit.toString(); } - - dispatch({ - type: SEND.FEE_LEVEL_CHANGE, - state - }); } -} + + dispatch({ + type: SEND.FEE_LEVEL_CHANGE, + state, + }); +}; // Manually triggered from user // Update gasPrice to recommended value -export const updateFeeLevels = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const { - account, - network - } = getState().selectedAccount; - if (!account || !network) return; - - const currentState: State = getState().sendForm; - const isToken: boolean = currentState.currency !== currentState.networkSymbol; - let gasLimit: string = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString(); - - // override custom settings - if (currentState.selectedFeeLevel.value === 'Custom') { - // update only gasPrice - currentState.selectedFeeLevel.gasPrice = currentState.recommendedGasPrice; - // leave gas limit as it was - gasLimit = currentState.gasLimit; - } - - const feeLevels: Array = getFeeLevels(network.symbol, currentState.recommendedGasPrice, gasLimit, currentState.selectedFeeLevel); - const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value); - if (!selectedFeeLevel) return; - - const state: State = { - ...currentState, - feeLevels, - selectedFeeLevel, - gasPrice: selectedFeeLevel.gasPrice, - gasPriceNeedsUpdate: false, - }; +export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const { + account, + network, + } = getState().selectedAccount; + if (!account || !network) return; - dispatch({ - type: SEND.UPDATE_FEE_LEVELS, - state - }); + const currentState: State = getState().sendForm; + const isToken: boolean = currentState.currency !== currentState.networkSymbol; + let gasLimit: string = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString(); + + // override custom settings + if (currentState.selectedFeeLevel.value === 'Custom') { + // update only gasPrice + currentState.selectedFeeLevel.gasPrice = currentState.recommendedGasPrice; + // leave gas limit as it was + gasLimit = currentState.gasLimit; } -} -export const onGasPriceChange = (gasPrice: string): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - const currentState: State = getState().sendForm; - const isToken: boolean = currentState.currency !== currentState.networkSymbol; + const feeLevels: Array = getFeeLevels(network.symbol, currentState.recommendedGasPrice, gasLimit, currentState.selectedFeeLevel); + const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value); + if (!selectedFeeLevel) return; + + const state: State = { + ...currentState, + feeLevels, + selectedFeeLevel, + gasPrice: selectedFeeLevel.gasPrice, + gasPriceNeedsUpdate: false, + }; + + dispatch({ + type: SEND.UPDATE_FEE_LEVELS, + state, + }); +}; - const touched = { ...currentState.touched }; - touched.gasPrice = true; +export const onGasPriceChange = (gasPrice: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const currentState: State = getState().sendForm; + const isToken: boolean = currentState.currency !== currentState.networkSymbol; - const state: State = { - ...currentState, - untouched: false, - touched, - gasPrice: gasPrice, - }; + const touched = { ...currentState.touched }; + touched.gasPrice = true; - if (currentState.selectedFeeLevel.value !== 'Custom') { - const customLevel = currentState.feeLevels.find(f => f.value === 'Custom'); - if (!customLevel) return; - state.selectedFeeLevel = customLevel; - } + const state: State = { + ...currentState, + untouched: false, + touched, + gasPrice, + }; - dispatch({ - type: SEND.GAS_PRICE_CHANGE, - state - }); + if (currentState.selectedFeeLevel.value !== 'Custom') { + const customLevel = currentState.feeLevels.find(f => f.value === 'Custom'); + if (!customLevel) return; + state.selectedFeeLevel = customLevel; } -} -export const onGasLimitChange = (gasLimit: string, updateFeeLevels: boolean = false): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - const currentState: State = getState().sendForm; - const isToken: boolean = currentState.currency !== currentState.networkSymbol; + dispatch({ + type: SEND.GAS_PRICE_CHANGE, + state, + }); +}; - const touched = { ...currentState.touched }; - touched.gasLimit = true; +export const onGasLimitChange = (gasLimit: string, updateFeeLevels: boolean = false): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const currentState: State = getState().sendForm; + const isToken: boolean = currentState.currency !== currentState.networkSymbol; + + const touched = { ...currentState.touched }; + touched.gasLimit = true; + + const state: State = { + ...currentState, + calculatingGasLimit: false, + untouched: false, + touched, + gasLimit, + }; + + if (currentState.selectedFeeLevel.value !== 'Custom') { + const customLevel = currentState.feeLevels.find(f => f.value === 'Custom'); + if (!customLevel) return; + state.selectedFeeLevel = customLevel; + } - const state: State = { - ...currentState, - calculatingGasLimit: false, - untouched: false, - touched, - gasLimit, - }; + dispatch({ + type: SEND.GAS_LIMIT_CHANGE, + state, + }); +}; - if (currentState.selectedFeeLevel.value !== 'Custom') { - const customLevel = currentState.feeLevels.find(f => f.value === 'Custom'); - if (!customLevel) return; - state.selectedFeeLevel = customLevel; - } +export const onNonceChange = (nonce: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const currentState: State = getState().sendForm; + const touched = { ...currentState.touched }; + touched.nonce = true; + + const state: State = { + ...currentState, + untouched: false, + touched, + nonce, + }; + + dispatch({ + type: SEND.NONCE_CHANGE, + state, + }); +}; - dispatch({ - type: SEND.GAS_LIMIT_CHANGE, - state - }); +export const onDataChange = (data: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const currentState: State = getState().sendForm; + const touched = { ...currentState.touched }; + touched.data = true; + + const state: State = { + ...currentState, + calculatingGasLimit: true, + untouched: false, + touched, + data, + }; + + if (currentState.selectedFeeLevel.value !== 'Custom') { + const customLevel = currentState.feeLevels.find(f => f.value === 'Custom'); + if (!customLevel) return; + state.selectedFeeLevel = customLevel; } -} -export const onNonceChange = (nonce: string): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const currentState: State = getState().sendForm; - const touched = { ...currentState.touched }; - touched.nonce = true; + dispatch({ + type: SEND.DATA_CHANGE, + state, + }); - const state: State = { - ...currentState, - untouched: false, - touched, - nonce, - }; + dispatch(estimateGasPrice()); +}; - dispatch({ - type: SEND.NONCE_CHANGE, - state - }); - } -} -export const onDataChange = (data: string): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const currentState: State = getState().sendForm; - const touched = { ...currentState.touched }; - touched.data = true; +const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { + web3, + network, + } = getState().selectedAccount; + if (!web3 || !network) return; - const state: State = { - ...currentState, - calculatingGasLimit: true, - untouched: false, - touched, - data, - }; + const w3 = web3.web3; - if (currentState.selectedFeeLevel.value !== 'Custom') { - const customLevel = currentState.feeLevels.find(f => f.value === 'Custom'); - if (!customLevel) return; - state.selectedFeeLevel = customLevel; - } + const state: State = getState().sendForm; + const requestedData = state.data; - dispatch({ - type: SEND.DATA_CHANGE, - state - }); + const re = /^[0-9A-Fa-f]+$/g; + if (!re.test(requestedData)) { + // to stop calculating + dispatch(onGasLimitChange(requestedData.length > 0 ? state.gasLimit : network.defaultGasLimit.toString())); + return; + } - dispatch( estimateGasPrice() ); + if (state.data.length < 1) { + // set default + dispatch(onGasLimitChange(network.defaultGasLimit.toString())); + return; } -} + // TODO: allow data starting with 0x ... + const data: string = `0x${state.data.length % 2 === 0 ? state.data : `0${state.data}`}`; + const gasLimit = await estimateGas(w3, { + to: '0x0000000000000000000000000000000000000000', + data, + value: w3.toHex(w3.toWei(state.amount, 'ether')), + gasPrice: w3.toHex(EthereumjsUnits.convert(state.gasPrice, 'gwei', 'wei')), + }); + + if (getState().sendForm.data === requestedData) { + dispatch(onGasLimitChange(gasLimit.toString())); + } +}; -const estimateGasPrice = (): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { +export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { + account, + network, + web3, + pending, + } = getState().selectedAccount; + if (!account || !web3 || !network) return; - const { - web3, - network - } = getState().selectedAccount; - if (!web3 || !network) return; + const currentState: State = getState().sendForm; - const w3 = web3.web3; + const isToken: boolean = currentState.currency !== currentState.networkSymbol; + const w3 = web3.web3; - const state: State = getState().sendForm; - const requestedData = state.data; + const address_n = account.addressPath; - const re = /^[0-9A-Fa-f]+$/g; - if (!re.test(requestedData)) { - // to stop calculating - dispatch( onGasLimitChange(requestedData.length > 0 ? state.gasLimit : network.defaultGasLimit.toString()) ); - return; - } + let data: string = `0x${currentState.data}`; + let txAmount: string = w3.toHex(w3.toWei(currentState.amount, 'ether')); + let txAddress: string = currentState.address; + if (isToken) { + const token: ?Token = findToken(getState().tokens, account.address, currentState.currency, account.deviceState); + if (!token) return; - if (state.data.length < 1) { - // set default - dispatch( onGasLimitChange(network.defaultGasLimit.toString()) ); - return; - } + const contract = web3.erc20.at(token.address); + const amountValue: string = new BigNumber(currentState.amount).times(Math.pow(10, token.decimals)).toString(10); - // TODO: allow data starting with 0x ... - const data: string = '0x' + (state.data.length % 2 === 0 ? state.data : '0' + state.data); - const gasLimit = await estimateGas(w3, { - to: '0x0000000000000000000000000000000000000000', - data, - value: w3.toHex(w3.toWei(state.amount, 'ether')), - gasPrice: w3.toHex( EthereumjsUnits.convert(state.gasPrice, 'gwei', 'wei') ), + data = contract.transfer.getData(currentState.address, amountValue, { + from: account.address, + gasLimit: currentState.gasLimit, + gasPrice: currentState.gasPrice, }); - - if (getState().sendForm.data === requestedData) { - dispatch( onGasLimitChange(gasLimit.toString()) ); - } + txAmount = '0x00'; + txAddress = token.address; } - -} -export const onSend = (): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { + const pendingNonce: number = stateUtils.getPendingNonce(pending); + const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce; + + console.warn('NONCE', nonce, account.nonce, pendingNonce); + + const txData = { + address_n, + // from: currentAddress.address + to: txAddress, + value: txAmount, + data, + chainId: web3.chainId, + nonce: w3.toHex(nonce), + gasLimit: w3.toHex(currentState.gasLimit), + gasPrice: w3.toHex(EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei')), + r: '', + s: '', + v: '', + }; + + const selected: ?TrezorDevice = getState().wallet.selectedDevice; + if (!selected) return; + + const signedTransaction = await TrezorConnect.ethereumSignTransaction({ + device: { + path: selected.path, + instance: selected.instance, + state: selected.state, + }, + useEmptyPassphrase: !selected.instance, + path: txData.address_n, + nonce: strip(txData.nonce), + gasPrice: strip(txData.gasPrice), + gasLimit: strip(txData.gasLimit), + to: strip(txData.to), + value: strip(txData.value), + data: strip(txData.data), + chainId: txData.chainId, + }); + + if (!signedTransaction || !signedTransaction.success) { + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Transaction error', + message: signedTransaction.payload.error, + cancelable: true, + actions: [], + }, + }); + return; + } - const { - account, - network, - web3, - pending - } = getState().selectedAccount; - if (!account || !web3 || !network) return; - - const currentState: State = getState().sendForm; - - const isToken: boolean = currentState.currency !== currentState.networkSymbol; - const w3 = web3.web3; - - const address_n = account.addressPath; - - let data: string = '0x' + currentState.data; - let txAmount: string = w3.toHex(w3.toWei(currentState.amount, 'ether')); - let txAddress: string = currentState.address; - if (isToken) { - const token: ?Token = findToken(getState().tokens, account.address, currentState.currency, account.deviceState); - if (!token) return; - - const contract = web3.erc20.at(token.address); - const amountValue: string = new BigNumber(currentState.amount).times( Math.pow(10, token.decimals) ).toString(10); - - data = contract.transfer.getData(currentState.address, amountValue, { - from: account.address, - gasLimit: currentState.gasLimit, - gasPrice: currentState.gasPrice - }); - txAmount = '0x00'; - txAddress = token.address; - } + txData.r = `0x${signedTransaction.payload.r}`; + txData.s = `0x${signedTransaction.payload.s}`; + txData.v = w3.toHex(signedTransaction.payload.v); - const pendingNonce: number = stateUtils.getPendingNonce(pending); - const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce; - - console.warn("NONCE", nonce, account.nonce, pendingNonce) - - const txData = { - address_n, - // from: currentAddress.address - to: txAddress, - value: txAmount, - data, - chainId: web3.chainId, - nonce: w3.toHex(nonce), - gasLimit: w3.toHex(currentState.gasLimit), - gasPrice: w3.toHex( EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei') ), - r: '', - s: '', - v: '' - } - const selected: ?TrezorDevice = getState().wallet.selectedDevice; - if (!selected) return; + try { + const tx = new EthereumjsTx(txData); + const serializedTx = `0x${tx.serialize().toString('hex')}`; + const txid: string = await pushTx(w3, serializedTx); - let signedTransaction = await TrezorConnect.ethereumSignTransaction({ - device: { - path: selected.path, - instance: selected.instance, - state: selected.state - }, - useEmptyPassphrase: !selected.instance, - path: txData.address_n, - nonce: strip(txData.nonce), - gasPrice: strip(txData.gasPrice), - gasLimit: strip(txData.gasLimit), - to: strip(txData.to), - value: strip(txData.value), - data: strip(txData.data), - chainId: txData.chainId + dispatch({ + type: SEND.TX_COMPLETE, + account, + selectedCurrency: currentState.currency, + amount: currentState.amount, + total: currentState.total, + tx, + nonce, + txid, + txData, }); - if (!signedTransaction || !signedTransaction.success) { + // clear session storage + dispatch(SessionStorageActions.clear()); - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'error', - title: 'Transaction error', - message: signedTransaction.payload.error, - cancelable: true, - actions: [ ] - } - }) - return; - } + // reset form + dispatch(init()); - txData.r = '0x' + signedTransaction.payload.r; - txData.s = '0x' + signedTransaction.payload.s; - txData.v = w3.toHex(signedTransaction.payload.v); - - - - try { - const tx = new EthereumjsTx(txData); - const serializedTx = '0x' + tx.serialize().toString('hex'); - const txid: string = await pushTx(w3, serializedTx); - - dispatch({ - type: SEND.TX_COMPLETE, - account: account, - selectedCurrency: currentState.currency, - amount: currentState.amount, - total: currentState.total, - tx, - nonce, - txid, - txData, - }); - - // clear session storage - dispatch( SessionStorageActions.clear() ); - - // reset form - dispatch( init() ); - - - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'success', - title: 'Transaction success', - message: `See transaction detail`, - cancelable: true, - actions: [] - } - }); - - } catch (error) { - - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'error', - title: 'Transaction error', - message: error.message || error, - cancelable: true, - actions: [ ] - } - }); - } + + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'success', + title: 'Transaction success', + message: `See transaction detail`, + cancelable: true, + actions: [], + }, + }); + } catch (error) { + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Transaction error', + message: error.message || error, + cancelable: true, + actions: [], + }, + }); } -} +}; export default { toggleAdvanced, @@ -978,4 +926,4 @@ export default { onNonceChange, onDataChange, onSend, -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/actions/SessionStorageActions.js b/src/js/actions/SessionStorageActions.js index a7fe9526..5aeed2b6 100644 --- a/src/js/actions/SessionStorageActions.js +++ b/src/js/actions/SessionStorageActions.js @@ -1,33 +1,30 @@ /* @flow */ -'use strict'; + import type { State as SendFormState } from '../reducers/SendFormReducer'; import type { ThunkAction, - GetState, + GetState, Dispatch, } from '~/flowtype'; const PREFIX: string = 'trezor:draft-tx:'; -export const save = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - if (typeof window.localStorage === 'undefined') return; - - const location = getState().router.location.pathname; - const state = getState().sendForm; - if (!state.untouched) { - try { - window.sessionStorage.setItem(`${PREFIX}${location}`, JSON.stringify(state) ); - } catch (error) { - console.error("Saving sessionStorage error: " + error) - } +export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + if (typeof window.localStorage === 'undefined') return; + + const location = getState().router.location.pathname; + const state = getState().sendForm; + if (!state.untouched) { + try { + window.sessionStorage.setItem(`${PREFIX}${location}`, JSON.stringify(state)); + } catch (error) { + console.error(`Saving sessionStorage error: ${error}`); } } -} +}; export const load = (location: string): ?SendFormState => { - if (typeof window.localStorage === 'undefined') return; try { @@ -39,20 +36,16 @@ export const load = (location: string): ?SendFormState => { } return state; } catch (error) { - console.error("Loading sessionStorage error: " + error) + console.error(`Loading sessionStorage error: ${error}`); } +}; - return; -} - -export const clear = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - if (typeof window.localStorage === 'undefined') return; - const location = getState().router.location.pathname; - try { - window.sessionStorage.removeItem(`${PREFIX}${location}`); - } catch (error) { - console.error("Clearing sessionStorage error: " + error) - } +export const clear = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + if (typeof window.localStorage === 'undefined') return; + const location = getState().router.location.pathname; + try { + window.sessionStorage.removeItem(`${PREFIX}${location}`); + } catch (error) { + console.error(`Clearing sessionStorage error: ${error}`); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/actions/SummaryActions.js b/src/js/actions/SummaryActions.js index 05dee428..9d3d3c91 100644 --- a/src/js/actions/SummaryActions.js +++ b/src/js/actions/SummaryActions.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import EthereumjsUtil from 'ethereumjs-util'; import * as SUMMARY from './constants/summary'; @@ -7,7 +7,9 @@ import * as TOKEN from './constants/token'; import { resolveAfter } from '../utils/promiseUtils'; import { initialState } from '../reducers/SummaryReducer'; -import type { ThunkAction, AsyncAction, Action, GetState, Dispatch } from '~/flowtype'; +import type { + ThunkAction, AsyncAction, Action, GetState, Dispatch, +} from '~/flowtype'; import type { State } from '../reducers/SummaryReducer'; import type { Token } from '../reducers/TokensReducer'; @@ -20,29 +22,21 @@ export type SummaryAction = { type: typeof SUMMARY.DETAILS_TOGGLE } -export const init = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - const state: State = { - ...initialState, - }; - - dispatch({ - type: SUMMARY.INIT, - state: state - }); - } -} +export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const state: State = { + ...initialState, + }; -export const dispose = (): Action => { - return { - type: SUMMARY.DISPOSE - } -} + dispatch({ + type: SUMMARY.INIT, + state, + }); +}; -export const onDetailsToggle = (): Action => { - return { - type: SUMMARY.DETAILS_TOGGLE - } -} +export const dispose = (): Action => ({ + type: SUMMARY.DISPOSE, +}); +export const onDetailsToggle = (): Action => ({ + type: SUMMARY.DETAILS_TOGGLE, +}); diff --git a/src/js/actions/TokenActions.js b/src/js/actions/TokenActions.js index c6f37d9f..3918fdfa 100644 --- a/src/js/actions/TokenActions.js +++ b/src/js/actions/TokenActions.js @@ -1,10 +1,12 @@ /* @flow */ -'use strict'; + import * as TOKEN from './constants/token'; import { getTokenInfoAsync, getTokenBalanceAsync } from './Web3Actions'; -import type { GetState, AsyncAction, Action, Dispatch } from '~/flowtype'; +import type { + GetState, AsyncAction, Action, Dispatch, +} from '~/flowtype'; import type { State, Token } from '../reducers/TokensReducer'; import type { Account } from '../reducers/AccountsReducer'; import type { NetworkToken } from '../reducers/LocalStorageReducer'; @@ -29,86 +31,71 @@ type SelectOptions = { // action from component -export const load = (input: string, network: string): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - - if (input.length < 1) return; - - const tokens = getState().localStorage.tokens[ network ]; - const value = input.toLowerCase(); - const result = tokens.filter(t => - t.symbol.toLowerCase().indexOf(value) >= 0 || - t.address.toLowerCase().indexOf(value) >= 0 || - t.name.toLowerCase().indexOf(value) >= 0 - ); - - if (result.length > 0) { - return { options: result }; - } else { - const web3instance = getState().web3.find(w3 => w3.network === network); - if (!web3instance) return; - - const info = await getTokenInfoAsync(web3instance.erc20, input); - if (info) { - return { - options: [ info ] - } - } - //await resolveAfter(300000); - //await resolveAfter(3000); - - } - return; - } -} +export const load = (input: string, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + if (input.length < 1) return; -export const add = (token: NetworkToken, account: Account): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - - const web3instance = getState().web3.find(w3 => w3.network === account.network); - if (!web3instance) return; - - const tkn: Token = { - loaded: false, - deviceState: account.deviceState, - network: account.network, - name: token.name, - symbol: token.symbol, - address: token.address, - ethAddress: account.address, - decimals: token.decimals, - balance: '0' - } - - dispatch({ - type: TOKEN.ADD, - payload: tkn - }); - - const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, tkn); - dispatch( setBalance(token.address, account.address, tokenBalance) ) - } -} + const tokens = getState().localStorage.tokens[network]; + const value = input.toLowerCase(); + const result = tokens.filter(t => t.symbol.toLowerCase().indexOf(value) >= 0 + || t.address.toLowerCase().indexOf(value) >= 0 + || t.name.toLowerCase().indexOf(value) >= 0); -export const setBalance = (tokenAddress: string, ethAddress: string, balance: string): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const newState: Array = [ ...getState().tokens ]; - let token: ?Token = newState.find(t => t.address === tokenAddress && t.ethAddress === ethAddress); - if (token) { - token.loaded = true; - token.balance = balance; - } - - dispatch({ - type: TOKEN.SET_BALANCE, - payload: newState - }) + if (result.length > 0) { + return { options: result }; } -} - -export const remove = (token: Token): Action => { - return { - type: TOKEN.REMOVE, - token + const web3instance = getState().web3.find(w3 => w3.network === network); + if (!web3instance) return; + + const info = await getTokenInfoAsync(web3instance.erc20, input); + if (info) { + return { + options: [info], + }; + } + //await resolveAfter(300000); + //await resolveAfter(3000); +}; + +export const add = (token: NetworkToken, account: Account): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const web3instance = getState().web3.find(w3 => w3.network === account.network); + if (!web3instance) return; + + const tkn: Token = { + loaded: false, + deviceState: account.deviceState, + network: account.network, + name: token.name, + symbol: token.symbol, + address: token.address, + ethAddress: account.address, + decimals: token.decimals, + balance: '0', + }; + + dispatch({ + type: TOKEN.ADD, + payload: tkn, + }); + + const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, tkn); + dispatch(setBalance(token.address, account.address, tokenBalance)); +}; + +export const setBalance = (tokenAddress: string, ethAddress: string, balance: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const newState: Array = [...getState().tokens]; + const token: ?Token = newState.find(t => t.address === tokenAddress && t.ethAddress === ethAddress); + if (token) { + token.loaded = true; + token.balance = balance; } -} \ No newline at end of file + + dispatch({ + type: TOKEN.SET_BALANCE, + payload: newState, + }); +}; + +export const remove = (token: Token): Action => ({ + type: TOKEN.REMOVE, + token, +}); \ No newline at end of file diff --git a/src/js/actions/TrezorConnectActions.js b/src/js/actions/TrezorConnectActions.js index 97824034..72278921 100644 --- a/src/js/actions/TrezorConnectActions.js +++ b/src/js/actions/TrezorConnectActions.js @@ -1,7 +1,9 @@ /* @flow */ -'use strict'; -import TrezorConnect, { UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT } from 'trezor-connect'; + +import TrezorConnect, { + UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, +} from 'trezor-connect'; import * as TOKEN from './constants/token'; import * as CONNECT from './constants/TrezorConnect'; import * as NOTIFICATION from './constants/notification'; @@ -20,17 +22,17 @@ import type { TransportMessage, DeviceMessageType, TransportMessageType, - UiMessageType + UiMessageType, } from 'trezor-connect'; -import type { +import type { Dispatch, GetState, Action, ThunkAction, AsyncAction, TrezorDevice, - RouterLocationState + RouterLocationState, } from '~/flowtype'; @@ -81,301 +83,276 @@ export type TrezorConnectAction = { }; -export const init = (): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - // set listeners - TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => { - // post event to reducers - const type: DeviceMessageType = event.type; // assert flow type - dispatch({ - type, - device: event.payload - }); +export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + // set listeners + TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => { + // post event to reducers + const type: DeviceMessageType = event.type; // assert flow type + dispatch({ + type, + device: event.payload, }); + }); - TrezorConnect.on(UI_EVENT, (event: UiMessage): void => { - // post event to reducers - const type: UiMessageType = event.type; // assert flow type - dispatch({ - type, - payload: event.payload - }); + TrezorConnect.on(UI_EVENT, (event: UiMessage): void => { + // post event to reducers + const type: UiMessageType = event.type; // assert flow type + dispatch({ + type, + payload: event.payload, }); + }); - TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => { - // post event to reducers - const type: TransportMessageType = event.type; // assert flow type - dispatch({ - type, - payload: event.payload - }); + TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => { + // post event to reducers + const type: TransportMessageType = event.type; // assert flow type + dispatch({ + type, + payload: event.payload, }); + }); - try { - await TrezorConnect.init({ - transportReconnect: true, - // connectSrc: 'https://localhost:8088/', - connectSrc: 'https://sisyfos.trezor.io/', - debug: false, - popup: false, - webusb: true, - pendingTransportEvent: (getState().devices.length < 1) - }); - } catch (error) { - // dispatch({ - // type: CONNECT.INITIALIZATION_ERROR, - // error - // }) - } + try { + await TrezorConnect.init({ + transportReconnect: true, + // connectSrc: 'https://localhost:8088/', + connectSrc: 'https://sisyfos.trezor.io/', + debug: false, + popup: false, + webusb: true, + pendingTransportEvent: (getState().devices.length < 1), + }); + } catch (error) { + // dispatch({ + // type: CONNECT.INITIALIZATION_ERROR, + // error + // }) } -} +}; // called after backend was initialized // set listeners for connect/disconnect -export const postInit = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { +export const postInit = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const handleDeviceConnect = (device: Device) => { + dispatch(initConnectedDevice(device)); + }; - const handleDeviceConnect = (device: Device) => { - dispatch( initConnectedDevice(device) ); - } + TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect); + TrezorConnect.off(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect); - TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect); - TrezorConnect.off(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect); + TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect); + TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect); - TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect); - TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect); + const { devices } = getState(); - const { devices } = getState(); + const { initialPathname, initialParams } = getState().wallet; - const { initialPathname, initialParams } = getState().wallet; + if (initialPathname) { + dispatch({ + type: WALLET.SET_INITIAL_URL, + // pathname: null, + // params: null + }); + } - if (initialPathname) { - dispatch({ - type: WALLET.SET_INITIAL_URL, - // pathname: null, - // params: null - }); - } + if (devices.length > 0) { + const unacquired: ?TrezorDevice = devices.find(d => d.unacquired); + if (unacquired) { + dispatch(onSelectDevice(unacquired)); + } else { + const latest: Array = sortDevices(devices); + const firstConnected: ?TrezorDevice = latest.find(d => d.connected); + dispatch(onSelectDevice(firstConnected || latest[0])); + + // TODO + if (initialParams) { + if (!initialParams.hasOwnProperty('network') && initialPathname !== getState().router.location.pathname) { + // dispatch( push(initialPathname) ); + } else { - if (devices.length > 0) { - const unacquired: ?TrezorDevice = devices.find(d => d.unacquired); - if (unacquired) { - dispatch( onSelectDevice(unacquired) ); - } else { - const latest: Array = sortDevices(devices); - const firstConnected: ?TrezorDevice = latest.find(d => d.connected); - dispatch( onSelectDevice(firstConnected || latest[0]) ); - - // TODO - if (initialParams) { - if (!initialParams.hasOwnProperty("network") && initialPathname !== getState().router.location.pathname) { - // dispatch( push(initialPathname) ); - } else { - - } } } } } -} - -const sortDevices = (devices: Array): Array => { - return devices.sort((a, b) => { - if (!a.ts || !b.ts) { - return -1; - } else { - return a.ts > b.ts ? -1 : 1; - } - }); -} +}; -export const initConnectedDevice = (device: TrezorDevice | Device): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const selected = getState().wallet.selectedDevice; - // if (!selected || (selected && selected.state)) { - dispatch( onSelectDevice(device) ); - // } - // if (device.unacquired && selected && selected.path !== device.path && !selected.connected) { - // dispatch( onSelectDevice(device) ); - // } else if (!selected) { - // dispatch( onSelectDevice(device) ); - // } +const sortDevices = (devices: Array): Array => devices.sort((a, b) => { + if (!a.ts || !b.ts) { + return -1; } -} + return a.ts > b.ts ? -1 : 1; +}); + +export const initConnectedDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const selected = getState().wallet.selectedDevice; + // if (!selected || (selected && selected.state)) { + dispatch(onSelectDevice(device)); + // } + // if (device.unacquired && selected && selected.path !== device.path && !selected.connected) { + // dispatch( onSelectDevice(device) ); + // } else if (!selected) { + // dispatch( onSelectDevice(device) ); + // } +}; // selection from Aside dropdown button // after device_connect event // or after acquiring device // device type could be local TrezorDevice or Device (from trezor-connect device_connect event) -export const onSelectDevice = (device: TrezorDevice | Device): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - // || device.isUsedElsewhere - - // switch to initial url and reset this value - - if (!device.features) { - dispatch( push(`/device/${ device.path }/acquire`) ); - } else if (device.features.bootloader_mode) { - dispatch( push(`/device/${ device.path }/bootloader`) ); - } else if (!device.features.initialized) { - dispatch( push(`/device/${ device.features.device_id }/initialize`) ); - } else if (typeof device.instance === 'number') { - dispatch( push(`/device/${ device.features.device_id }:${ device.instance }`) ); - } else { - - const deviceId: string = device.features.device_id; - const urlParams: RouterLocationState = getState().router.location.state; - // let url: string = `/device/${ device.features.device_id }/network/ethereum/account/0`; - let url: string = `/device/${ deviceId }`; - let instance: ?number; - // check if device is not TrezorDevice type - if (!device.hasOwnProperty('ts')) { - // its device from trezor-connect (called in initConnectedDevice triggered by device_connect event) - // need to lookup if there are unavailable instances - const available: Array = getState().devices.filter(d => d.path === device.path); - const latest: Array = sortDevices(available); - - if (latest.length > 0 && latest[0].instance) { - url += `:${ latest[0].instance }`; - instance = latest[0].instance; - } +export const onSelectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + // || device.isUsedElsewhere + + // switch to initial url and reset this value + + if (!device.features) { + dispatch(push(`/device/${device.path}/acquire`)); + } else if (device.features.bootloader_mode) { + dispatch(push(`/device/${device.path}/bootloader`)); + } else if (!device.features.initialized) { + dispatch(push(`/device/${device.features.device_id}/initialize`)); + } else if (typeof device.instance === 'number') { + dispatch(push(`/device/${device.features.device_id}:${device.instance}`)); + } else { + const deviceId: string = device.features.device_id; + const urlParams: RouterLocationState = getState().router.location.state; + // let url: string = `/device/${ device.features.device_id }/network/ethereum/account/0`; + let url: string = `/device/${deviceId}`; + let instance: ?number; + // check if device is not TrezorDevice type + if (!device.hasOwnProperty('ts')) { + // its device from trezor-connect (called in initConnectedDevice triggered by device_connect event) + // need to lookup if there are unavailable instances + const available: Array = getState().devices.filter(d => d.path === device.path); + const latest: Array = sortDevices(available); + + if (latest.length > 0 && latest[0].instance) { + url += `:${latest[0].instance}`; + instance = latest[0].instance; } - // check if current location is not set to this device - //dispatch( push(`/device/${ device.features.device_id }/network/etc/account/0`) ); + } + // check if current location is not set to this device + //dispatch( push(`/device/${ device.features.device_id }/network/etc/account/0`) ); - if (urlParams.deviceInstance !== instance || urlParams.device !== deviceId) { - dispatch( push(url) ); - } + if (urlParams.deviceInstance !== instance || urlParams.device !== deviceId) { + dispatch(push(url)); } } -} - -export const switchToFirstAvailableDevice = (): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { +}; - const { devices } = getState(); - if (devices.length > 0) { - // TODO: Priority: - // 1. First Unacquired - // 2. First connected - // 3. Saved with latest timestamp - const unacquired = devices.find(d => d.unacquired); - if (unacquired) { - dispatch( initConnectedDevice(unacquired) ); - } else { - const latest: Array = sortDevices(devices); - const firstConnected: ?TrezorDevice = latest.find(d => d.connected); - dispatch( onSelectDevice(firstConnected || latest[0]) ); - } +export const switchToFirstAvailableDevice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { devices } = getState(); + if (devices.length > 0) { + // TODO: Priority: + // 1. First Unacquired + // 2. First connected + // 3. Saved with latest timestamp + const unacquired = devices.find(d => d.unacquired); + if (unacquired) { + dispatch(initConnectedDevice(unacquired)); } else { - dispatch( push('/') ); + const latest: Array = sortDevices(devices); + const firstConnected: ?TrezorDevice = latest.find(d => d.connected); + dispatch(onSelectDevice(firstConnected || latest[0])); } + } else { + dispatch(push('/')); } -} +}; -export const getSelectedDeviceState = (): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const selected = getState().wallet.selectedDevice; - if (selected +export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const selected = getState().wallet.selectedDevice; + if (selected && selected.connected && (selected.features && !selected.features.bootloader_mode && selected.features.initialized) && !selected.state) { + const response = await TrezorConnect.getDeviceState({ + device: { + path: selected.path, + instance: selected.instance, + state: selected.state, + }, + useEmptyPassphrase: !selected.instance, + }); - const response = await TrezorConnect.getDeviceState({ - device: { - path: selected.path, - instance: selected.instance, - state: selected.state + if (response && response.success) { + dispatch({ + type: CONNECT.AUTH_DEVICE, + device: selected, + state: response.payload.state, + }); + } else { + dispatch({ + type: NOTIFICATION.ADD, + payload: { + devicePath: selected.path, + type: 'error', + title: 'Authentication error', + message: response.payload.error, + cancelable: false, + actions: [ + { + label: 'Try again', + callback: () => { + dispatch({ + type: NOTIFICATION.CLOSE, + payload: { devicePath: selected.path }, + }); + dispatch(getSelectedDeviceState()); + }, + }, + ], }, - useEmptyPassphrase: !selected.instance, }); - - if (response && response.success) { - dispatch({ - type: CONNECT.AUTH_DEVICE, - device: selected, - state: response.payload.state - }); - } else { - dispatch({ - type: NOTIFICATION.ADD, - payload: { - devicePath: selected.path, - type: 'error', - title: 'Authentication error', - message: response.payload.error, - cancelable: false, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch( { - type: NOTIFICATION.CLOSE, - payload: { devicePath: selected.path } - }); - dispatch( getSelectedDeviceState() ); - } - } - ] - } - }); - } } } -} +}; -export const deviceDisconnect = (device: Device): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { +export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const selected: ?TrezorDevice = getState().wallet.selectedDevice; - const selected: ?TrezorDevice = getState().wallet.selectedDevice; - - if (device && device.features) { - if (selected && selected.features && selected.features.device_id === device.features.device_id) { - dispatch( DiscoveryActions.stop(selected) ); - } + if (device && device.features) { + if (selected && selected.features && selected.features.device_id === device.features.device_id) { + dispatch(DiscoveryActions.stop(selected)); + } - const instances = getState().devices.filter(d => d.features && d.state && !d.remember && d.features.device_id === device.features.device_id); - if (instances.length > 0) { - dispatch({ - type: CONNECT.REMEMBER_REQUEST, - device: instances[0], - instances, - }); - } + const instances = getState().devices.filter(d => d.features && d.state && !d.remember && d.features.device_id === device.features.device_id); + if (instances.length > 0) { + dispatch({ + type: CONNECT.REMEMBER_REQUEST, + device: instances[0], + instances, + }); } } -} +}; -export const coinChanged = (network: ?string): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - const selected: ?TrezorDevice = getState().wallet.selectedDevice; - if (!selected) return; +export const coinChanged = (network: ?string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const selected: ?TrezorDevice = getState().wallet.selectedDevice; + if (!selected) return; - dispatch( DiscoveryActions.stop(selected) ); + dispatch(DiscoveryActions.stop(selected)); - if (network) { - dispatch( DiscoveryActions.start(selected, network) ); - } + if (network) { + dispatch(DiscoveryActions.start(selected, network)); } -} +}; export function reload(): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - } + }; } export function acquire(): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - const selected: ?TrezorDevice = getState().wallet.selectedDevice; if (!selected) return; dispatch({ type: CONNECT.START_ACQUIRING, - }) + }); - const response = await TrezorConnect.getFeatures({ + const response = await TrezorConnect.getFeatures({ device: { path: selected.path, }, @@ -398,47 +375,41 @@ export function acquire(): AsyncAction { // } // } // ] - } - }) + }, + }); } dispatch({ type: CONNECT.STOP_ACQUIRING, - }) - } + }); + }; } -export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - if (device.features) { - const devUrl: string = `${device.features.device_id}${ device.instance ? `:${ device.instance}` : '' }`; - dispatch( push( `/device/${ devUrl }/settings` ) ); - } +export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + if (device.features) { + const devUrl: string = `${device.features.device_id}${device.instance ? `:${device.instance}` : ''}`; + dispatch(push(`/device/${devUrl}/settings`)); } -} +}; // called from Aside - device menu (forget single instance) -export const forget = (device: TrezorDevice): Action => { - return { - type: CONNECT.FORGET_REQUEST, - device - }; -} - -export const duplicateDevice = (device: TrezorDevice): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - dispatch({ - type: CONNECT.TRY_TO_DUPLICATE, - device - }) - } -} +export const forget = (device: TrezorDevice): Action => ({ + type: CONNECT.FORGET_REQUEST, + device, +}); + +export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + dispatch({ + type: CONNECT.TRY_TO_DUPLICATE, + device, + }); +}; export function addAccount(): ThunkAction { return (dispatch: Dispatch, getState: GetState): void => { const selected = getState().wallet.selectedDevice; if (!selected) return; - dispatch( DiscoveryActions.start(selected, getState().router.location.state.network, true) ); // TODO: network nicer - } + dispatch(DiscoveryActions.start(selected, getState().router.location.state.network, true)); // TODO: network nicer + }; } diff --git a/src/js/actions/WalletActions.js b/src/js/actions/WalletActions.js index 067e6bd4..763bb8db 100644 --- a/src/js/actions/WalletActions.js +++ b/src/js/actions/WalletActions.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import { LOCATION_CHANGE } from 'react-router-redux'; import * as WALLET from './constants/wallet'; @@ -8,20 +8,20 @@ import * as stateUtils from '../reducers/utils'; import type { Device } from 'trezor-connect'; import type - { +{ Account, Coin, Discovery, Token, Web3Instance, - TrezorDevice, - RouterLocationState, + TrezorDevice, + RouterLocationState, ThunkAction, AsyncAction, Action, - Dispatch, + Dispatch, GetState, - State + State, } from '~/flowtype'; export type WalletAction = { @@ -47,82 +47,66 @@ export type WalletAction = { devices: Array } -export const init = (): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - const updateOnlineStatus = (event) => { - dispatch({ - type: WALLET.ONLINE_STATUS, - online: navigator.onLine - }) - } - window.addEventListener('online', updateOnlineStatus); - window.addEventListener('offline', updateOnlineStatus); - } -} - -export const onBeforeUnload = (): WalletAction => { - return { - type: WALLET.ON_BEFORE_UNLOAD - } -} - -export const toggleDeviceDropdown = (opened: boolean): WalletAction => { - return { - type: WALLET.TOGGLE_DEVICE_DROPDOWN, - opened - } -} +export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const updateOnlineStatus = (event) => { + dispatch({ + type: WALLET.ONLINE_STATUS, + online: navigator.onLine, + }); + }; + window.addEventListener('online', updateOnlineStatus); + window.addEventListener('offline', updateOnlineStatus); +}; + +export const onBeforeUnload = (): WalletAction => ({ + type: WALLET.ON_BEFORE_UNLOAD, +}); + +export const toggleDeviceDropdown = (opened: boolean): WalletAction => ({ + type: WALLET.TOGGLE_DEVICE_DROPDOWN, + opened, +}); // This method will be called after each DEVICE.CONNECT action // if connected device has different "passphrase_protection" settings than saved instances // all saved instances will be removed immediately inside DevicesReducer // This method will clear leftovers associated with removed instances from reducers. // (DiscoveryReducer, AccountReducer, TokensReducer) -export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => { - return (dispatch: Dispatch, getState: GetState): void => { - - if (!device.features) return; +export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + if (!device.features) return; - const affectedDevices = prevState.devices.filter(d => - d.features + const affectedDevices = prevState.devices.filter(d => d.features && d.features.device_id === device.features.device_id - && d.features.passphrase_protection !== device.features.passphrase_protection - ); + && d.features.passphrase_protection !== device.features.passphrase_protection); - if (affectedDevices.length > 0) { - dispatch({ - type: WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA, - devices: affectedDevices, - }) - } + if (affectedDevices.length > 0) { + dispatch({ + type: WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA, + devices: affectedDevices, + }); } -} - - -export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { - - const locationChange: boolean = action.type === LOCATION_CHANGE; - const state: State = getState(); - - // handle devices state change (from trezor-connect events or location change) - if (locationChange || prevState.devices !== state.devices) { - const device = stateUtils.getSelectedDevice(state); - if (state.wallet.selectedDevice !== device) { - if (device && stateUtils.isSelectedDevice(state.wallet.selectedDevice, device)) { - dispatch({ - type: WALLET.UPDATE_SELECTED_DEVICE, - device - }); - } else { - dispatch({ - type: WALLET.SET_SELECTED_DEVICE, - device - }); - } +}; + + +export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const locationChange: boolean = action.type === LOCATION_CHANGE; + const state: State = getState(); + + // handle devices state change (from trezor-connect events or location change) + if (locationChange || prevState.devices !== state.devices) { + const device = stateUtils.getSelectedDevice(state); + if (state.wallet.selectedDevice !== device) { + if (device && stateUtils.isSelectedDevice(state.wallet.selectedDevice, device)) { + dispatch({ + type: WALLET.UPDATE_SELECTED_DEVICE, + device, + }); + } else { + dispatch({ + type: WALLET.SET_SELECTED_DEVICE, + device, + }); } } - } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/actions/Web3Actions.js b/src/js/actions/Web3Actions.js index a65ff05b..056f9ab4 100644 --- a/src/js/actions/Web3Actions.js +++ b/src/js/actions/Web3Actions.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import Web3 from 'web3'; import HDKey from 'hdkey'; @@ -7,27 +7,27 @@ import HDKey from 'hdkey'; import EthereumjsUtil from 'ethereumjs-util'; import EthereumjsTx from 'ethereumjs-tx'; import TrezorConnect from 'trezor-connect'; +import type { ContractFactory, EstimateGasOptions } from 'web3'; +import type BigNumber from 'bignumber.js'; +import type { TransactionStatus, TransactionReceipt } from 'web3'; import { strip } from '../utils/ethUtils'; import * as WEB3 from './constants/web3'; import * as PENDING from './constants/pendingTx'; -import * as AccountsActions from '../actions/AccountsActions'; -import * as TokenActions from '../actions/TokenActions'; +import * as AccountsActions from './AccountsActions'; +import * as TokenActions from './TokenActions'; -import type { +import type { Dispatch, GetState, Action, AsyncAction, } from '~/flowtype'; -import type { ContractFactory, EstimateGasOptions } from 'web3'; -import type BigNumber from 'bignumber.js'; import type { Account } from '../reducers/AccountsReducer'; import type { PendingTx } from '../reducers/PendingTxReducer'; import type { Web3Instance } from '../reducers/Web3Reducer'; import type { Token } from '../reducers/TokensReducer'; import type { NetworkToken } from '../reducers/LocalStorageReducer'; -import type { TransactionStatus, TransactionReceipt } from 'web3'; export type Web3Action = { type: typeof WEB3.READY, @@ -53,10 +53,9 @@ export type Web3UpdateGasPriceAction = { export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - const { config, ERC20Abi } = getState().localStorage; - const coin = config.coins[ coinIndex ]; + const coin = config.coins[coinIndex]; if (!coin) { // all instances done dispatch({ @@ -72,12 +71,12 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { if (instance) { const currentHost = instance.currentProvider.host; - let currentHostIndex: number = urls.indexOf(currentHost); + const currentHostIndex: number = urls.indexOf(currentHost); if (currentHostIndex + 1 < urls.length) { web3host = urls[currentHostIndex + 1]; } else { - console.error("TODO: Backend " + network + " not working", instance.currentProvider ); + console.error(`TODO: Backend ${network} not working`, instance.currentProvider); dispatch({ type: WEB3.CREATE, @@ -88,17 +87,17 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { erc20: instance.eth.contract(ERC20Abi), latestBlock: '0', gasPrice: '0', - } + }, }); // try next coin - dispatch( init(null, coinIndex + 1) ); + dispatch(init(null, coinIndex + 1)); return; } } //const instance = new Web3(window.web3.currentProvider); - const web3 = new Web3( new Web3.providers.HttpProvider(web3host) ); + const web3 = new Web3(new Web3.providers.HttpProvider(web3host)); // instance = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') ); // UBQ //instance = new Web3( new Web3.providers.HttpProvider('https://node.expanse.tech/') ); // EXP @@ -112,7 +111,7 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { // initial check if backend is running if (!web3.currentProvider.isConnected()) { // try different url - dispatch( init(web3, coinIndex) ); + dispatch(init(web3, coinIndex)); return; } @@ -122,12 +121,12 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { type: WEB3.CREATE, instance: { network, - web3: web3, + web3, chainId: coin.chainId, erc20, latestBlock: '0', gasPrice: '0', - } + }, }); // dispatch({ @@ -136,15 +135,13 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { // gasPrice // }); - - // console.log("GET CHAIN", instance.version.network) // instance.version.getWhisper((err, shh) => { // console.log("-----whisperrr", error, shh) // }) - + // const sshFilter = instance.ssh.filter('latest'); // sshFilter.watch((error, blockHash) => { @@ -157,10 +154,9 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { const onBlockMined = async (error: ?Error, blockHash: ?string) => { if (error) { - window.setTimeout(() => { // try again - onBlockMined(new Error("manually_triggered_error"), undefined); + onBlockMined(new Error('manually_triggered_error'), undefined); }, 30000); } @@ -168,7 +164,7 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { dispatch({ type: WEB3.BLOCK_UPDATED, network, - blockHash + blockHash, }); } @@ -181,70 +177,66 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { // dispatch( getBalance(account) ); // TODO: check if nonce was updated, - // update tokens balance, + // update tokens balance, // update account balance, // update pending transactions } - dispatch( getBalance(account) ); + dispatch(getBalance(account)); // dispatch( getNonce(account) ); - } const tokens = getState().tokens.filter(t => t.network === network); for (const token of tokens) { - dispatch( getTokenBalance(token) ); + dispatch(getTokenBalance(token)); } - dispatch( getGasPrice(network) ); + dispatch(getGasPrice(network)); const pending = getState().pending.filter(p => p.network === network); for (const tx of pending) { - dispatch( getTransactionReceipt(tx) ); + dispatch(getTransactionReceipt(tx)); } - - } + }; // latestBlockFilter.watch(onBlockMined); - onBlockMined(new Error("manually_triggered_error"), undefined); + onBlockMined(new Error('manually_triggered_error'), undefined); // init next coin - dispatch( init(web3, coinIndex + 1) ); - + dispatch(init(web3, coinIndex + 1)); + // let instance2 = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') ); // console.log("INIT WEB3", instance, instance2); // instance2.eth.getGasPrice((error, gasPrice) => { // console.log("---gasss price from UBQ", gasPrice) // }); - } + }; } export function getGasPrice(network: string): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - const index: number = getState().web3.findIndex(w3 => w3.network === network); - const web3instance = getState().web3[ index ]; + const web3instance = getState().web3[index]; const web3 = web3instance.web3; web3.eth.getGasPrice((error, gasPrice) => { if (!error) { if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) { dispatch({ type: WEB3.GAS_PRICE_UPDATED, - network: network, - gasPrice + network, + gasPrice, }); } } }); - } + }; } export function getBalance(account: Account): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0]; const web3: Web3 = web3instance.web3; @@ -256,42 +248,39 @@ export function getBalance(account: Account): AsyncAction { account.address, account.network, account.deviceState, - newBalance + newBalance, )); // dispatch( loadHistory(addr) ); } } }); - } + }; } export function getTokenBalance(token: Token): AsyncAction { return async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.filter(w3 => w3.network === token.network)[0]; const web3 = web3instance.web3; const contract = web3instance.erc20.at(token.address); contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => { if (balance) { - const newBalance: string = balance.dividedBy( Math.pow(10, token.decimals) ).toString(10); + const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10); if (newBalance !== token.balance) { dispatch(TokenActions.setBalance( token.address, token.ethAddress, - newBalance + newBalance, )); } } }); - } + }; } export function getNonce(account: Account): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0]; const web3 = web3instance.web3; @@ -302,158 +291,138 @@ export function getNonce(account: Account): AsyncAction { } } }); - } + }; } -export const getTransactionReceipt = (tx: PendingTx): AsyncAction => { - return async (dispatch: Dispatch, getState: GetState): Promise => { +export const getTransactionReceipt = (tx: PendingTx): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const web3instance = getState().web3.filter(w3 => w3.network === tx.network)[0]; + const web3 = web3instance.web3; - const web3instance = getState().web3.filter(w3 => w3.network === tx.network)[0]; - const web3 = web3instance.web3; - - web3.eth.getTransaction(tx.id, (error: Error, status: TransactionStatus) => { - if (!error && !status) { - dispatch({ - type: PENDING.TX_NOT_FOUND, - tx, - }) - } else if (status && status.blockNumber) { - web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => { - if (receipt) { - if (status.gas !== receipt.gasUsed) { - dispatch({ - type: PENDING.TX_TOKEN_ERROR, - tx - }) - } + web3.eth.getTransaction(tx.id, (error: Error, status: TransactionStatus) => { + if (!error && !status) { + dispatch({ + type: PENDING.TX_NOT_FOUND, + tx, + }); + } else if (status && status.blockNumber) { + web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => { + if (receipt) { + if (status.gas !== receipt.gasUsed) { dispatch({ - type: PENDING.TX_RESOLVED, + type: PENDING.TX_TOKEN_ERROR, tx, - receipt - }) + }); } - - }); - } - }); - } -} - -export const getTransaction = (web3: Web3, txid: string): Promise => { - return new Promise((resolve, reject) => { - web3.eth.getTransaction(txid, (error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); + dispatch({ + type: PENDING.TX_RESOLVED, + tx, + receipt, + }); + } + }); + } }); -} +}; -export const getBalanceAsync = (web3: Web3, address: string): Promise => { - return new Promise((resolve, reject) => { - web3.eth.getBalance(address, (error: Error, result: BigNumber) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); +export const getTransaction = (web3: Web3, txid: string): Promise => new Promise((resolve, reject) => { + web3.eth.getTransaction(txid, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } }); -} - -export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise => { - return new Promise((resolve, reject) => { - - const contract = erc20.at(token.address); - contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => { - if (error) { - reject(error); - } else { - const newBalance: string = balance.dividedBy( Math.pow(10, token.decimals) ).toString(10); - resolve(newBalance); - } - }); +}); + +export const getBalanceAsync = (web3: Web3, address: string): Promise => new Promise((resolve, reject) => { + web3.eth.getBalance(address, (error: Error, result: BigNumber) => { + if (error) { + reject(error); + } else { + resolve(result); + } }); -} +}); + +export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise => new Promise((resolve, reject) => { + const contract = erc20.at(token.address); + contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => { + if (error) { + reject(error); + } else { + const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10); + resolve(newBalance); + } + }); +}); + +export const getNonceAsync = (web3: Web3, address: string): Promise => new Promise((resolve, reject) => { + web3.eth.getTransactionCount(address, (error: Error, result: number) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); +}); -export const getNonceAsync = (web3: Web3, address: string): Promise => { - return new Promise((resolve, reject) => { - web3.eth.getTransactionCount(address, (error: Error, result: number) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); + +export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise => new Promise((resolve, reject) => { + const contract = erc20.at(address, (error, res) => { + // console.warn("callback", error, res) }); -} + const info: NetworkToken = { + address, + name: '', + symbol: '', + decimals: 0, + }; -export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise => { - return new Promise((resolve, reject) => { + contract.name.call((error: Error, name: string) => { + if (error) { + resolve(null); + return; + } + info.name = name; - const contract = erc20.at(address, (error, res) => { - // console.warn("callback", error, res) - }); - const info: NetworkToken = { - address, - name: '', - symbol: '', - decimals: 0 - }; - - contract.name.call((error: Error, name: string) => { + contract.symbol.call((error: Error, symbol: string) => { if (error) { resolve(null); return; - } else { - info.name = name; } - - contract.symbol.call((error: Error, symbol: string) => { - if (error) { - resolve(null); - return; + info.symbol = symbol; + + + contract.decimals.call((error: Error, decimals: BigNumber) => { + if (decimals) { + info.decimals = decimals.toNumber(); + resolve(info); } else { - info.symbol = symbol; + resolve(null); } - - contract.decimals.call((error: Error, decimals: BigNumber) => { - if (decimals) { - info.decimals = decimals.toNumber(); - resolve(info); - } else { - resolve(null); - } - }); - }) + }); }); }); -} - -export const estimateGas = (web3: Web3, options: EstimateGasOptions): Promise => { - return new Promise((resolve, reject) => { - web3.eth.estimateGas(options, (error: ?Error, gas: ?number) => { - if (error) { - reject(error); - } else if (typeof gas === 'number'){ - resolve(gas); - } - }); - }) -} - -export const pushTx = (web3: Web3, tx: any): Promise => { - return new Promise((resolve, reject) => { - web3.eth.sendRawTransaction(tx, (error: Error, result: string) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }) -} \ No newline at end of file +}); + +export const estimateGas = (web3: Web3, options: EstimateGasOptions): Promise => new Promise((resolve, reject) => { + web3.eth.estimateGas(options, (error: ?Error, gas: ?number) => { + if (error) { + reject(error); + } else if (typeof gas === 'number') { + resolve(gas); + } + }); +}); + +export const pushTx = (web3: Web3, tx: any): Promise => new Promise((resolve, reject) => { + web3.eth.sendRawTransaction(tx, (error: Error, result: string) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); +}); \ No newline at end of file diff --git a/src/js/actions/constants/TrezorConnect.js b/src/js/actions/constants/TrezorConnect.js index 0e4f3cd7..a49371c5 100644 --- a/src/js/actions/constants/TrezorConnect.js +++ b/src/js/actions/constants/TrezorConnect.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + //regExp1 : string = '(.*)' //regExp2 : '$1' = '$1' diff --git a/src/js/actions/constants/account.js b/src/js/actions/constants/account.js index 11c72a4d..18075a00 100644 --- a/src/js/actions/constants/account.js +++ b/src/js/actions/constants/account.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const INIT: 'account__init' = 'account__init'; export const DISPOSE: 'account__dispose' = 'account__dispose'; diff --git a/src/js/actions/constants/discovery.js b/src/js/actions/constants/discovery.js index 977f6e00..1fca3045 100644 --- a/src/js/actions/constants/discovery.js +++ b/src/js/actions/constants/discovery.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const START: 'discovery__start' = 'discovery__start'; export const STOP: 'discovery__stop' = 'discovery__stop'; diff --git a/src/js/actions/constants/localStorage.js b/src/js/actions/constants/localStorage.js index 9c73badd..402a23a8 100644 --- a/src/js/actions/constants/localStorage.js +++ b/src/js/actions/constants/localStorage.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const SAVE: 'storage__save' = 'storage__save'; export const READY: 'storage__ready' = 'storage__ready'; diff --git a/src/js/actions/constants/log.js b/src/js/actions/constants/log.js index 9c33fb60..a08a84cf 100644 --- a/src/js/actions/constants/log.js +++ b/src/js/actions/constants/log.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const OPEN: 'log__open' = 'log__open'; export const CLOSE: 'log__close' = 'log__close'; diff --git a/src/js/actions/constants/modal.js b/src/js/actions/constants/modal.js index a0949ebd..917b2029 100644 --- a/src/js/actions/constants/modal.js +++ b/src/js/actions/constants/modal.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const ON_PASSPHRASE_CHANGE: 'action__on_passphrase_change' = 'action__on_passphrase_change'; export const ON_PASSPHRASE_SHOW: 'action__on_passphrase_show' = 'action__on_passphrase_show'; diff --git a/src/js/actions/constants/notification.js b/src/js/actions/constants/notification.js index 0b14d504..c9147409 100644 --- a/src/js/actions/constants/notification.js +++ b/src/js/actions/constants/notification.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const ADD: 'notification__add' = 'notification__add'; export const CLOSE: 'notification__close' = 'notification__close'; diff --git a/src/js/actions/constants/pendingTx.js b/src/js/actions/constants/pendingTx.js index 08a58b36..c2cfc0c2 100644 --- a/src/js/actions/constants/pendingTx.js +++ b/src/js/actions/constants/pendingTx.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage'; export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved'; diff --git a/src/js/actions/constants/receive.js b/src/js/actions/constants/receive.js index cbf243e2..21d39d15 100644 --- a/src/js/actions/constants/receive.js +++ b/src/js/actions/constants/receive.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const INIT: 'receive__init' = 'receive__init'; export const DISPOSE: 'receive__dispose' = 'receive__dispose'; diff --git a/src/js/actions/constants/send.js b/src/js/actions/constants/send.js index 35c70969..08528692 100644 --- a/src/js/actions/constants/send.js +++ b/src/js/actions/constants/send.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const INIT: 'send__init' = 'send__init'; export const DISPOSE: 'send__dispose' = 'send__dispose'; diff --git a/src/js/actions/constants/summary.js b/src/js/actions/constants/summary.js index ebb82c95..ae002582 100644 --- a/src/js/actions/constants/summary.js +++ b/src/js/actions/constants/summary.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const INIT: 'summary__init' = 'summary__init'; export const DISPOSE: 'summary__dispose' = 'summary__dispose'; diff --git a/src/js/actions/constants/token.js b/src/js/actions/constants/token.js index 5878c5b5..f2ce1ebc 100644 --- a/src/js/actions/constants/token.js +++ b/src/js/actions/constants/token.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const ADD: 'token__add' = 'token__add'; export const REMOVE: 'token__remove' = 'token__remove'; diff --git a/src/js/actions/constants/wallet.js b/src/js/actions/constants/wallet.js index c19c73ba..4aa8cd4f 100644 --- a/src/js/actions/constants/wallet.js +++ b/src/js/actions/constants/wallet.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const ON_BEFORE_UNLOAD: 'wallet__on_before_unload' = 'wallet__on_before_unload'; export const TOGGLE_DEVICE_DROPDOWN: 'wallet__toggle_dropdown' = 'wallet__toggle_dropdown'; diff --git a/src/js/actions/constants/web3.js b/src/js/actions/constants/web3.js index ad55001f..232db739 100644 --- a/src/js/actions/constants/web3.js +++ b/src/js/actions/constants/web3.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + export const START: 'web3__start' = 'web3__start'; export const STOP: 'web3__stop' = 'web3__stop'; diff --git a/src/js/components/common/Footer.js b/src/js/components/common/Footer.js index c32a6159..b61f2003 100644 --- a/src/js/components/common/Footer.js +++ b/src/js/components/common/Footer.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import React from 'react'; import { bindActionCreators } from 'redux'; @@ -12,26 +12,20 @@ type Props = { toggle: typeof LogActions.toggle } -const Footer = (props: Props): React$Element => { - return ( -

- ); -} +const Footer = (props: Props): React$Element => ( + +); + +export default connect( + (state: State) => ({ -export default connect( - (state: State) => { - return { - - } - }, - (dispatch: Dispatch) => { - return { - toggle: bindActionCreators(LogActions.toggle, dispatch), - }; - } + }), + (dispatch: Dispatch) => ({ + toggle: bindActionCreators(LogActions.toggle, dispatch), + }), )(Footer); diff --git a/src/js/components/common/Header.js b/src/js/components/common/Header.js index 9ebbd0df..318da14d 100644 --- a/src/js/components/common/Header.js +++ b/src/js/components/common/Header.js @@ -1,30 +1,28 @@ /* @flow */ -'use strict'; + import React from 'react'; -const Header = (): React$Element => { - return ( -
-
- - - - - - - - - -
- TREZOR - Docs - Blog - Support -
+const Header = (): React$Element => ( +
+
+ + + + + + + + + + -
- ); -} +
+
+); export default Header; \ No newline at end of file diff --git a/src/js/components/common/LoaderCircle.js b/src/js/components/common/LoaderCircle.js index 9a780b25..fe4f4827 100644 --- a/src/js/components/common/LoaderCircle.js +++ b/src/js/components/common/LoaderCircle.js @@ -1,17 +1,16 @@ /* @flow */ -'use strict'; + import React from 'react'; export default (props: { size: string, label?: string }): React$Element => { - const style = { - width: `${props.size}px`, - height: `${props.size}px`, - } - + width: `${props.size}px`, + height: `${props.size}px`, + }; + return ( -
+

{ props.label }

@@ -19,4 +18,4 @@ export default (props: { size: string, label?: string }): React$Element
); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/components/common/Log.js b/src/js/components/common/Log.js index dbfdc7a0..e0b8ef2d 100644 --- a/src/js/components/common/Log.js +++ b/src/js/components/common/Log.js @@ -1,5 +1,5 @@ /* @flow */ -'use strict'; + import React from 'react'; import { bindActionCreators } from 'redux'; @@ -14,34 +14,29 @@ type Props = { } const Log = (props: Props): ?React$Element => { - if (!props.log.opened) - return null; + if (!props.log.opened) return null; // const entries = props.log.entries.map(entry => { // return ( // ) // }) - + return (
- +
-
-

Verify message

- - - - - - -
- - ); -} +const SignVerify = () => ( +
+
+

Sign message

+ +