Fixed some eslint problems

pull/1/head
Vladimír Volek 6 years ago
parent e2c014f595
commit 7627aade89

@ -0,0 +1 @@
solidity

@ -19,7 +19,7 @@
"build": "rm -rf build && webpack --config ./webpack/config.prod.babel.js --progress", "build": "rm -rf build && webpack --config ./webpack/config.prod.babel.js --progress",
"flow": "flow check src/js", "flow": "flow check src/js",
"test": "", "test": "",
"lint": "npx eslint ./" "lint": "npx eslint ./ --fix"
}, },
"dependencies": { "dependencies": {
"color-hash": "^1.0.3", "color-hash": "^1.0.3",

@ -8,27 +8,27 @@ const replacePrefix = (path, opts = [], sourceFile) => {
const options = [].concat(opts); const options = [].concat(opts);
if (typeof path === 'string') { if (typeof path === 'string') {
if (path.indexOf('~/') === 0) { if (path.indexOf('~/') === 0) {
return path.replace('~/', `${ cwd }/src/`); return path.replace('~/', `${cwd}/src/`);
} }
} }
return path; return path;
} };
export default ({ 'types': t }) => { export default ({ types: t }) => {
const visitor = { const visitor = {
CallExpression(path, state) { CallExpression(path, state) {
if (path.node.callee.name !== 'require') { if (path.node.callee.name !== 'require') {
return; return;
} }
const args = path.node.arguments; const args = path.node.arguments;
if (!args.length) { if (!args.length) {
return; return;
} }
const firstArg = traverseExpression(t, args[0]); const firstArg = traverseExpression(t, args[0]);
if (firstArg) { if (firstArg) {
firstArg.value = replacePrefix(firstArg.value, state.opts, state.file.opts.filename); firstArg.value = replacePrefix(firstArg.value, state.opts, state.file.opts.filename);
} }
@ -45,14 +45,14 @@ export default ({ 'types': t }) => {
if (path.node.source) { if (path.node.source) {
path.node.source.value = replacePrefix(path.node.source.value, state.opts, state.file.opts.filename); path.node.source.value = replacePrefix(path.node.source.value, state.opts, state.file.opts.filename);
} }
} },
}; };
return { return {
'visitor': { visitor: {
Program(path, state) { Program(path, state) {
path.traverse(visitor, state); path.traverse(visitor, state);
} },
} },
}; };
} };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
declare module CSSModule { declare module CSSModule {
declare var exports: { [key: string]: string }; declare var exports: { [key: string]: string };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import type { import type {
Store as ReduxStore, Store as ReduxStore,
@ -9,7 +9,7 @@ import type {
ThunkAction as ReduxThunkAction, ThunkAction as ReduxThunkAction,
AsyncAction as ReduxAsyncAction, AsyncAction as ReduxAsyncAction,
ThunkDispatch as ReduxThunkDispatch, ThunkDispatch as ReduxThunkDispatch,
PlainDispatch as ReduxPlainDispatch PlainDispatch as ReduxPlainDispatch,
} from 'redux'; } from 'redux';
import type { Reducers, ReducersState } from '~/js/reducers'; import type { Reducers, ReducersState } from '~/js/reducers';
@ -43,7 +43,7 @@ import type {
import type { RouterAction, LocationState } from 'react-router-redux'; import type { RouterAction, LocationState } from 'react-router-redux';
export type TrezorDevice = { export type TrezorDevice = {
remember: boolean; // device should be remembered remember: boolean; // device should be remembered
connected: boolean; // device is connected connected: boolean; // device is connected
available: boolean; // device cannot be used because of features.passphrase_protection is different then expected available: boolean; // device cannot be used because of features.passphrase_protection is different then expected
path: string; path: string;
@ -87,19 +87,19 @@ type IFrameHandshake = {
payload: any payload: any
} }
export type Action = export type Action =
RouterAction RouterAction
| IFrameHandshake | IFrameHandshake
| TransportEventAction | TransportEventAction
| DeviceEventAction | DeviceEventAction
| UiEventAction | UiEventAction
| SelectedAccountAction | SelectedAccountAction
| AccountAction | AccountAction
| DiscoveryAction | DiscoveryAction
| StorageAction | StorageAction
| LogAction | LogAction
| ModalAction | ModalAction
| NotificationAction | NotificationAction
| PendingTxAction | PendingTxAction
| ReceiveAction | ReceiveAction

@ -7,22 +7,22 @@ declare module 'bignumber.js' {
declare type ROUND_HALF_EVEN = 2 declare type ROUND_HALF_EVEN = 2
declare type ROUND_UP = 3 declare type ROUND_UP = 3
declare type RM = ROUND_DOWN | ROUND_HALF_UP | ROUND_HALF_EVEN | ROUND_UP declare type RM = ROUND_DOWN | ROUND_HALF_UP | ROUND_HALF_EVEN | ROUND_UP
declare class T_BigNumber { declare class T_BigNumber {
// Properties // Properties
static DP: number; static DP: number;
static RM: RM; static RM: RM;
static E_NEG: number; static E_NEG: number;
static E_POS: number; static E_POS: number;
c: Array<DIGIT>; c: Array<DIGIT>;
e: number; e: number;
s: -1 | 1; s: -1 | 1;
// Constructors // Constructors
static (value: $npm$big$number$object): T_BigNumber; static (value: $npm$big$number$object): T_BigNumber;
constructor(value: $npm$big$number$object): T_BigNumber; constructor(value: $npm$big$number$object): T_BigNumber;
// Methods // Methods
abs(): BigNumber; abs(): BigNumber;
cmp(n: $npm$big$number$object): $npm$cmp$result; cmp(n: $npm$big$number$object): $npm$cmp$result;
@ -51,7 +51,7 @@ declare module 'bignumber.js' {
valueOf(): string; valueOf(): string;
toJSON(): string; toJSON(): string;
} }
//declare module.exports: typeof T_BigNumber //declare module.exports: typeof T_BigNumber
declare export default typeof T_BigNumber; declare export default typeof T_BigNumber;
} }

@ -16,16 +16,16 @@ declare module 'ethereum-types' {
| 'mether' | 'mether'
| 'gether' | 'gether'
| 'tether' | 'tether'
declare export type EthereumAddressT = string declare export type EthereumAddressT = string
declare export type EthereumBlockNumberT = number declare export type EthereumBlockNumberT = number
declare export type EthereumBlockHashT = string declare export type EthereumBlockHashT = string
declare export type EthereumTransactionHashT = string declare export type EthereumTransactionHashT = string
// end data types // end data types
// start contract types // start contract types
declare export type EthereumWatchErrorT = ?Object declare export type EthereumWatchErrorT = ?Object
declare export type EthereumEventT<A> = { declare export type EthereumEventT<A> = {
address: EthereumAddressT, address: EthereumAddressT,
args: A, args: A,
@ -38,35 +38,32 @@ declare module 'ethereum-types' {
transactionLogIndex: string, transactionLogIndex: string,
type: 'mined' // TODO: what other types are there? type: 'mined' // TODO: what other types are there?
} }
// this represents the setup object returned from truffle-contract // this represents the setup object returned from truffle-contract
// we use it to get a known contact `at(address)` (ie. for POATokenContract addresses) // we use it to get a known contact `at(address)` (ie. for POATokenContract addresses)
declare export type EthereumContractSetupT<A> = { declare export type EthereumContractSetupT<A> = {
at: EthereumAddressT => Promise<A> at: EthereumAddressT => Promise<A>
} }
declare export type EthereumSendTransactionOptionsT = { declare export type EthereumSendTransactionOptionsT = {
from: EthereumAddressT, from: EthereumAddressT,
gas: number, gas: number,
value?: number value?: number
} }
declare export type EthereumSendTransactionT = EthereumSendTransactionOptionsT => Promise< declare export type EthereumSendTransactionT = EthereumSendTransactionOptionsT => Promise<EthereumTransactionHashT>
EthereumTransactionHashT
>
// TODO(mattgstevens): it would be nice to have an Generic type for a Contract instance // TODO(mattgstevens): it would be nice to have an Generic type for a Contract instance
// similar to the EthererumWatchEventT // similar to the EthererumWatchEventT
// //
// declare export type SendTransactionContractT = interface .sendTransaction(EthereumAddressT) // declare export type SendTransactionContractT = interface .sendTransaction(EthereumAddressT)
// declare export type WatchableContractT = <A>(error: Object, response: A) // declare export type WatchableContractT = <A>(error: Object, response: A)
// declare export type EthereumContractWatcherT = (options: { // declare export type EthereumContractWatcherT = (options: {
// fromBlock?: EthereumBlockNumberT, // fromBlock?: EthereumBlockNumberT,
// toBlock?: EthereumBlockNumberT, // toBlock?: EthereumBlockNumberT,
// address?: EthereumAddressT // address?: EthereumAddressT
// }) => * // }) => *
// end contract data // end contract data
} }

@ -1,9 +1,9 @@
// flow-typed signature: 59b0c4be0e1408f21e2446be96c79804 // flow-typed signature: 59b0c4be0e1408f21e2446be96c79804
// flow-typed version: 9092387fd2/react-redux_v5.x.x/flow_>=v0.54.x // 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 S = State
@ -32,9 +32,7 @@ declare module "react-redux" {
declare type Context = { store: Store<*, *> }; declare type Context = { store: Store<*, *> };
declare type ComponentWithDefaultProps<DP: {}, P: {}, CP: P> = Class< declare type ComponentWithDefaultProps<DP: {}, P: {}, CP: P> = Class<React$Component<CP>> & { defaultProps: DP };
React$Component<CP>
> & { defaultProps: DP };
declare class ConnectedComponentWithDefaultProps< declare class ConnectedComponentWithDefaultProps<
OP, OP,
@ -55,13 +53,9 @@ declare module "react-redux" {
state: void state: void
} }
declare type ConnectedComponentWithDefaultPropsClass<OP, DP, CP> = Class< declare type ConnectedComponentWithDefaultPropsClass<OP, DP, CP> = Class<ConnectedComponentWithDefaultProps<OP, DP, CP>>;
ConnectedComponentWithDefaultProps<OP, DP, CP>
>;
declare type ConnectedComponentClass<OP, P> = Class< declare type ConnectedComponentClass<OP, P> = Class<ConnectedComponent<OP, P>>;
ConnectedComponent<OP, P>
>;
declare type Connector<OP, P> = (<DP: {}, CP: {}>( declare type Connector<OP, P> = (<DP: {}, CP: {}>(
component: ComponentWithDefaultProps<DP, P, CP> component: ComponentWithDefaultProps<DP, P, CP>

@ -1,4 +1,4 @@
declare module "react-router-dom" { declare module 'react-router-dom' {
declare export class BrowserRouter extends React$Component<{ declare export class BrowserRouter extends React$Component<{
basename?: string, basename?: string,
forceRefresh?: boolean, forceRefresh?: boolean,
@ -6,21 +6,21 @@ declare module "react-router-dom" {
keyLength?: number, keyLength?: number,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class HashRouter extends React$Component<{ declare export class HashRouter extends React$Component<{
basename?: string, basename?: string,
getUserConfirmation?: GetUserConfirmation, getUserConfirmation?: GetUserConfirmation,
hashType?: "slash" | "noslash" | "hashbang", hashType?: "slash" | "noslash" | "hashbang",
children?: React$Node children?: React$Node
}> {} }> {}
declare export class Link extends React$Component<{ declare export class Link extends React$Component<{
className?: string, className?: string,
to: string | LocationShape, to: string | LocationShape,
replace?: boolean, replace?: boolean,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class NavLink extends React$Component<{ declare export class NavLink extends React$Component<{
to: string | LocationShape, to: string | LocationShape,
activeClassName?: string, activeClassName?: string,
@ -32,7 +32,7 @@ declare module "react-router-dom" {
exact?: boolean, exact?: boolean,
strict?: boolean strict?: boolean
}> {} }> {}
// NOTE: Below are duplicated from react-router. If updating these, please // NOTE: Below are duplicated from react-router. If updating these, please
// update the react-router and react-router-native types as well. // update the react-router and react-router-native types as well.
declare export type Location = { declare export type Location = {
@ -42,16 +42,16 @@ declare module "react-router-dom" {
state?: any, state?: any,
key?: string key?: string
}; };
declare export type LocationShape = { declare export type LocationShape = {
pathname?: string, pathname?: string,
search?: string, search?: string,
hash?: string, hash?: string,
state?: any state?: any
}; };
declare export type HistoryAction = "PUSH" | "REPLACE" | "POP"; declare export type HistoryAction = "PUSH" | "REPLACE" | "POP";
declare export type RouterHistory = { declare export type RouterHistory = {
length: number, length: number,
location: Location, location: Location,
@ -72,37 +72,37 @@ declare module "react-router-dom" {
index?: number, index?: number,
entries?: Array<Location> entries?: Array<Location>
}; };
declare export type Match = { declare export type Match = {
params: { [key: string]: ?string }, params: { [key: string]: ?string },
isExact: boolean, isExact: boolean,
path: string, path: string,
url: string url: string
}; };
declare export type ContextRouter = {| declare export type ContextRouter = {|
history: RouterHistory, history: RouterHistory,
location: Location, location: Location,
match: Match, match: Match,
staticContext?: StaticRouterContext, staticContext?: StaticRouterContext,
|}; |};
declare export type GetUserConfirmation = ( declare export type GetUserConfirmation = (
message: string, message: string,
callback: (confirmed: boolean) => void callback: (confirmed: boolean) => void
) => void; ) => void;
declare type StaticRouterContext = { declare type StaticRouterContext = {
url?: string url?: string
}; };
declare export class StaticRouter extends React$Component<{ declare export class StaticRouter extends React$Component<{
basename?: string, basename?: string,
location?: string | Location, location?: string | Location,
context: StaticRouterContext, context: StaticRouterContext,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class MemoryRouter extends React$Component<{ declare export class MemoryRouter extends React$Component<{
initialEntries?: Array<LocationShape | string>, initialEntries?: Array<LocationShape | string>,
initialIndex?: number, initialIndex?: number,
@ -110,22 +110,22 @@ declare module "react-router-dom" {
keyLength?: number, keyLength?: number,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class Router extends React$Component<{ declare export class Router extends React$Component<{
history: RouterHistory, history: RouterHistory,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class Prompt extends React$Component<{ declare export class Prompt extends React$Component<{
message: string | ((location: Location) => string | boolean), message: string | ((location: Location) => string | boolean),
when?: boolean when?: boolean
}> {} }> {}
declare export class Redirect extends React$Component<{ declare export class Redirect extends React$Component<{
to: string | LocationShape, to: string | LocationShape,
push?: boolean push?: boolean
}> {} }> {}
declare export class Route extends React$Component<{ declare export class Route extends React$Component<{
component?: React$ComponentType<*>, component?: React$ComponentType<*>,
render?: (router: ContextRouter) => React$Node, render?: (router: ContextRouter) => React$Node,
@ -134,22 +134,22 @@ declare module "react-router-dom" {
exact?: boolean, exact?: boolean,
strict?: boolean strict?: boolean
}> {} }> {}
declare export class Switch extends React$Component<{ declare export class Switch extends React$Component<{
children?: React$Node children?: React$Node
}> {} }> {}
declare export function withRouter<P>( declare export function withRouter<P>(
Component: React$ComponentType<{| ...ContextRouter, ...P |}> Component: React$ComponentType<{| ...ContextRouter, ...P |}>
): React$ComponentType<P>; ): React$ComponentType<P>;
declare type MatchPathOptions = { declare type MatchPathOptions = {
path?: string, path?: string,
exact?: boolean, exact?: boolean,
sensitive?: boolean, sensitive?: boolean,
strict?: boolean strict?: boolean
}; };
declare export function matchPath( declare export function matchPath(
pathname: string, pathname: string,
options?: MatchPathOptions | string options?: MatchPathOptions | string

@ -1,12 +1,11 @@
import type { import type {
RouterHistory, RouterHistory,
Location as RouterLocation Location as RouterLocation,
} from 'react-router'; } from 'react-router';
declare module "react-router-redux" { declare module 'react-router-redux' {
// custom state for location // custom state for location
declare export type LocationState = {[key: string] : string}; declare export type LocationState = {[key: string]: string};
declare export type Location = { declare export type Location = {
pathname: string, pathname: string,
@ -16,7 +15,7 @@ declare module "react-router-redux" {
state: LocationState state: LocationState
} }
declare export var LOCATION_CHANGE: "@@router/LOCATION_CHANGE"; declare export var LOCATION_CHANGE: "@@router/LOCATION_CHANGE";
declare export type RouterAction = { declare export type RouterAction = {
type: typeof LOCATION_CHANGE, type: typeof LOCATION_CHANGE,
@ -32,7 +31,7 @@ declare module "react-router-redux" {
declare export function go(a: string): RouterAction; declare export function go(a: string): RouterAction;
declare export function goBack(): RouterAction; declare export function goBack(): RouterAction;
declare export function goForward(): RouterAction; declare export function goForward(): RouterAction;
//declare export function routerReducer<S, A>(state?: S, action: A): S; //declare export function routerReducer<S, A>(state?: S, action: A): S;
declare export function routerReducer(state?: State, action: any): State; declare export function routerReducer(state?: State, action: any): State;
declare export function routerMiddleware(history: any): any; declare export function routerMiddleware(history: any): any;

@ -1,4 +1,4 @@
declare module "react-router" { declare module 'react-router' {
// NOTE: many of these are re-exported by react-router-dom and // 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 // react-router-native, so when making changes, please be sure to update those
// as well. // as well.
@ -9,16 +9,16 @@ declare module "react-router" {
state?: any, state?: any,
key?: string key?: string
}; };
declare export type LocationShape = { declare export type LocationShape = {
pathname?: string, pathname?: string,
search?: string, search?: string,
hash?: string, hash?: string,
state?: any state?: any
}; };
declare export type HistoryAction = "PUSH" | "REPLACE" | "POP"; declare export type HistoryAction = "PUSH" | "REPLACE" | "POP";
declare export type RouterHistory = { declare export type RouterHistory = {
length: number, length: number,
location: Location, location: Location,
@ -39,37 +39,37 @@ declare module "react-router" {
index?: number, index?: number,
entries?: Array<Location> entries?: Array<Location>
}; };
declare export type Match = { declare export type Match = {
params: { [key: string]: ?string }, params: { [key: string]: ?string },
isExact: boolean, isExact: boolean,
path: string, path: string,
url: string url: string
}; };
declare export type ContextRouter = {| declare export type ContextRouter = {|
history: RouterHistory, history: RouterHistory,
location: Location, location: Location,
match: Match, match: Match,
staticContext?: StaticRouterContext staticContext?: StaticRouterContext
|}; |};
declare export type GetUserConfirmation = ( declare export type GetUserConfirmation = (
message: string, message: string,
callback: (confirmed: boolean) => void callback: (confirmed: boolean) => void
) => void; ) => void;
declare type StaticRouterContext = { declare type StaticRouterContext = {
url?: string url?: string
}; };
declare export class StaticRouter extends React$Component<{ declare export class StaticRouter extends React$Component<{
basename?: string, basename?: string,
location?: string | Location, location?: string | Location,
context: StaticRouterContext, context: StaticRouterContext,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class MemoryRouter extends React$Component<{ declare export class MemoryRouter extends React$Component<{
initialEntries?: Array<LocationShape | string>, initialEntries?: Array<LocationShape | string>,
initialIndex?: number, initialIndex?: number,
@ -77,22 +77,22 @@ declare module "react-router" {
keyLength?: number, keyLength?: number,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class Router extends React$Component<{ declare export class Router extends React$Component<{
history: RouterHistory, history: RouterHistory,
children?: React$Node children?: React$Node
}> {} }> {}
declare export class Prompt extends React$Component<{ declare export class Prompt extends React$Component<{
message: string | ((location: Location) => string | true), message: string | ((location: Location) => string | true),
when?: boolean when?: boolean
}> {} }> {}
declare export class Redirect extends React$Component<{ declare export class Redirect extends React$Component<{
to: string | LocationShape, to: string | LocationShape,
push?: boolean push?: boolean
}> {} }> {}
declare export class Route extends React$Component<{ declare export class Route extends React$Component<{
component?: React$ComponentType<*>, component?: React$ComponentType<*>,
render?: (router: ContextRouter) => React$Node, render?: (router: ContextRouter) => React$Node,
@ -101,15 +101,15 @@ declare module "react-router" {
exact?: boolean, exact?: boolean,
strict?: boolean strict?: boolean
}> {} }> {}
declare export class Switch extends React$Component<{ declare export class Switch extends React$Component<{
children?: React$Node children?: React$Node
}> {} }> {}
declare export function withRouter<P>( declare export function withRouter<P>(
Component: React$ComponentType<{| ...ContextRouter, ...P |}> Component: React$ComponentType<{| ...ContextRouter, ...P |}>
): React$ComponentType<P>; ): React$ComponentType<P>;
declare type MatchPathOptions = { declare type MatchPathOptions = {
path?: string, path?: string,
exact?: boolean, exact?: boolean,

@ -1,5 +1,4 @@
declare module 'redux' { declare module 'redux' {
/* /*
S = State S = State
@ -19,7 +18,7 @@ declare module 'redux' {
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>; declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>; declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
/* NEW: Dispatch is now a combination of these different dispatch types */ /* NEW: Dispatch is now a combination of these different dispatch types */
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S ,A>; declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A>;
declare export type MiddlewareAPI<S, A> = { declare export type MiddlewareAPI<S, A> = {
// dispatch: Dispatch<S, A>; // dispatch: Dispatch<S, A>;

@ -33,7 +33,7 @@ declare module 'web3' {
network: string; network: string;
// and many more // and many more
} }
} }
declare export type EstimateGasOptions = { declare export type EstimateGasOptions = {
@ -124,10 +124,8 @@ declare module 'web3' {
} }
//
// //
//
/*declare module 'web3' { /*declare module 'web3' {

@ -1,5 +1,4 @@
/* @flow */ /* @flow */
'use strict';
import * as ACCOUNT from './constants/account'; import * as ACCOUNT from './constants/account';
import type { Action, TrezorDevice } from '~/flowtype'; import type { Action, TrezorDevice } from '~/flowtype';
@ -22,7 +21,7 @@ export type AccountCreateAction = {
network: string, network: string,
index: number, index: number,
path: Array<number>, path: Array<number>,
address: string address: string
} }
export type AccountSetBalanceAction = { export type AccountSetBalanceAction = {
@ -41,22 +40,18 @@ export type AccountSetNonceAction = {
nonce: number nonce: number
} }
export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => { export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => ({
return { type: ACCOUNT.SET_BALANCE,
type: ACCOUNT.SET_BALANCE, address,
address, network,
network, deviceState,
deviceState, balance,
balance });
}
} export const setNonce = (address: string, network: string, deviceState: string, nonce: number): Action => ({
type: ACCOUNT.SET_NONCE,
export const setNonce = (address: string, network: string, deviceState: string, nonce: number): Action => { address,
return { network,
type: ACCOUNT.SET_NONCE, deviceState,
address, nonce,
network, });
deviceState,
nonce
}
}

@ -1,19 +1,21 @@
/* @flow */ /* @flow */
'use strict';
import TrezorConnect from 'trezor-connect'; import TrezorConnect from 'trezor-connect';
import HDKey from 'hdkey';
import EthereumjsUtil from 'ethereumjs-util';
import * as DISCOVERY from './constants/discovery'; import * as DISCOVERY from './constants/discovery';
import * as ACCOUNT from './constants/account'; import * as ACCOUNT from './constants/account';
import * as TOKEN from './constants/token'; import * as TOKEN from './constants/token';
import * as NOTIFICATION from './constants/notification'; 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 { getNonceAsync, getBalanceAsync, getTokenBalanceAsync } from './Web3Actions';
import { setBalance as setTokenBalance } from './TokenActions'; 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'; import type { Discovery, State } from '../reducers/DiscoveryReducer';
export type DiscoveryAction = { export type DiscoveryAction = {
@ -50,320 +52,297 @@ export type DiscoveryCompleteAction = {
network: string network: string
} }
export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => { export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const selected = getState().wallet.selectedDevice;
if (!selected) {
const selected = getState().wallet.selectedDevice; // TODO: throw error
if (!selected) { console.error('Start discovery: no selected device', device);
// TODO: throw error return;
console.error("Start discovery: no selected device", device) } if (selected.path !== device.path) {
return; console.error('Start discovery: requested device is not selected', device, selected);
} else if (selected.path !== device.path) { return;
console.error("Start discovery: requested device is not selected", device, selected) } if (!selected.state) {
return; console.warn("Start discovery: Selected device wasn't authenticated yet...");
} else if (!selected.state) { return;
console.warn("Start discovery: Selected device wasn't authenticated yet...") } if (selected.connected && !selected.available) {
return; console.warn('Start discovery: Selected device is unavailable...');
} else if (selected.connected && !selected.available) { return;
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);
const web3 = getState().web3.find(w3 => w3.network === network);
if (!web3) {
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { console.error('Start discovery: Web3 does not exist', network);
dispatch({ return;
type: DISCOVERY.WAITING_FOR_DEVICE, }
device,
network
});
return;
}
if (!discoveryProcess) { if (!web3.web3.currentProvider.isConnected()) {
dispatch( begin(device, network) ); console.error('Start discovery: Web3 is not connected', network);
return; dispatch({
} else { type: DISCOVERY.WAITING_FOR_BACKEND,
if (discoveryProcess.completed && !ignoreCompleted) { device,
dispatch({ network,
type: DISCOVERY.COMPLETE, });
device, return;
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 => { const discovery: State = getState().discovery;
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { 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({ dispatch({
type: DISCOVERY.WAITING_FOR_DEVICE, type: DISCOVERY.WAITING_FOR_DEVICE,
device, device,
network network,
}); });
return;
}
// get xpub from TREZOR if (!discoveryProcess) {
const response = await TrezorConnect.getPublicKey({ dispatch(begin(device, network));
device: { } else if (discoveryProcess.completed && !ignoreCompleted) {
path: device.path, dispatch({
instance: device.instance, type: DISCOVERY.COMPLETE,
state: device.state device,
}, network,
path: coinToDiscover.bip44,
keepSession: true, // acquire and hold session
useEmptyPassphrase: !device.instance,
}); });
} 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<void> => {
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 // handle TREZOR response error
if (!response.success) { if (!response.success) {
// TODO: check message // TODO: check message
console.warn("DISCOVERY ERROR", response) 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<number> = 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<void> => {
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({ dispatch({
type: NOTIFICATION.ADD, type: NOTIFICATION.ADD,
payload: { payload: {
type: 'error', type: 'error',
title: 'Discovery error', title: 'Address validation error',
message: response.payload.error, message: `Addresses are different. TREZOR: ${trezorAddress} HDKey: ${ethAddress}`,
cancelable: true, cancelable: true,
actions: [ actions: [
{ {
label: 'Try again', label: 'Try again',
callback: () => { callback: () => {
dispatch(start(device, network)) dispatch(start(device, discoveryProcess.network));
} },
} },
] ],
} },
}) });
return; return;
} }
} else {
// check for interruption // handle TREZOR communication error
let discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === device.state && d.network === network);
if (discoveryProcess && discoveryProcess.interrupted) return;
const basePath: Array<number> = response.payload.path;
// send data to reducer
dispatch({ dispatch({
type: DISCOVERY.START, type: NOTIFICATION.ADD,
network: coinToDiscover.network, payload: {
device, type: 'error',
publicKey: response.payload.publicKey, title: 'Address validation error',
chainCode: response.payload.chainCode, message: verifyAddress.payload.error,
basePath, cancelable: true,
actions: [
{
label: 'Try again',
callback: () => {
dispatch(start(device, discoveryProcess.network));
},
},
],
},
}); });
return;
dispatch( start(device, network) );
} }
}
const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => { const web3instance = getState().web3.find(w3 => w3.network === network);
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { if (!web3instance) return;
const completed: boolean = discoveryProcess.completed; const balance = await getBalanceAsync(web3instance.web3, ethAddress);
discoveryProcess.completed = false; 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 addressIsEmpty = nonce < 1 && !balance.greaterThan(0);
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;
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 if (addressIsEmpty) {
const verifyAddress = await TrezorConnect.ethereumGetAddress({ // release acquired sesssion
await TrezorConnect.getFeatures({
device: { device: {
path: device.path, path: device.path,
instance: device.instance, instance: device.instance,
state: device.state state: device.state,
}, },
path, keepSession: false,
showOnTrezor: false,
keepSession: true,
useEmptyPassphrase: !device.instance, useEmptyPassphrase: !device.instance,
}); });
if (discoveryProcess.interrupted) return; if (discoveryProcess.interrupted) return;
// TODO: with block-book (Martin) dispatch({
// const discoveryA = await TrezorConnect.accountDiscovery({ type: DISCOVERY.COMPLETE,
// device: { device,
// path: device.path, network,
// 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
});
}
} }
} };
export const restore = (): ThunkAction => { export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const selected = getState().wallet.selectedDevice;
const selected = getState().wallet.selectedDevice;
if (selected && selected.connected && selected.features) { if (selected && selected.connected && selected.features) {
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.waitingForDevice); const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.waitingForDevice);
if (discoveryProcess) { if (discoveryProcess) {
dispatch( start(selected, discoveryProcess.network) ); dispatch(start(selected, discoveryProcess.network));
}
} }
} }
} };
// TODO: rename method to something intuitive // TODO: rename method to something intuitive
// there is no discovery process but it should be // 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 // 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 // try to start discovery after CONNECT.AUTH_DEVICE action
export const check = (): ThunkAction => { export const check = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const selected = getState().wallet.selectedDevice;
const selected = getState().wallet.selectedDevice; if (!selected) return;
if (!selected) return;
const urlParams = getState().router.location.state;
const urlParams = getState().router.location.state; if (urlParams.network) {
if (urlParams.network) { const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.network === urlParams.network);
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.network === urlParams.network); if (!discoveryProcess) {
if (!discoveryProcess) { dispatch(start(selected, urlParams.network));
dispatch( start(selected, urlParams.network) );
}
} }
} }
} };
export const stop = (device: TrezorDevice): Action => { export const stop = (device: TrezorDevice): Action => ({
// TODO: release devices session type: DISCOVERY.STOP,
// corner case switch /eth to /etc (discovery start stop - should not be async) device,
return { });
type: DISCOVERY.STOP,
device
}
}

@ -1,2 +1 @@
/* @flow */ /* @flow */
'use strict';

@ -1,15 +1,17 @@
/* @flow */ /* @flow */
'use strict';
import * as CONNECT from './constants/TrezorConnect'; import * as CONNECT from './constants/TrezorConnect';
import * as ACCOUNT from './constants/account'; import * as ACCOUNT from './constants/account';
import * as TOKEN from './constants/token'; import * as TOKEN from './constants/token';
import * as DISCOVERY from './constants/discovery'; import * as DISCOVERY from './constants/discovery';
import * as STORAGE from './constants/localStorage'; 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 { 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 type { Config, Coin, TokensCollection } from '../reducers/LocalStorageReducer';
import AppConfigJSON from '~/data/appConfig.json'; import AppConfigJSON from '~/data/appConfig.json';
@ -28,24 +30,21 @@ export type StorageAction = {
error: string, error: string,
}; };
export const loadData = (): ThunkAction => { export const loadData = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { // check if local storage is available
// let available: boolean = true;
// check if local storage is available // if (typeof window.localStorage === 'undefined') {
// let available: boolean = true; // available = false;
// if (typeof window.localStorage === 'undefined') { // } else {
// available = false; // try {
// } else { // window.localStorage.setItem('ethereum_wallet', true);
// try { // } catch (error) {
// window.localStorage.setItem('ethereum_wallet', true); // available = false;
// } catch (error) { // }
// available = false; // }
// }
// } dispatch(loadTokensFromJSON());
};
dispatch( loadTokensFromJSON() );
}
}
// const parseConfig = (json: JSON): Config => { // const parseConfig = (json: JSON): Config => {
@ -68,13 +67,13 @@ export const loadData = (): ThunkAction => {
// if (json.config && json.hasOwnProperty('coins') && Array.isArray(json.coins)) { // if (json.config && json.hasOwnProperty('coins') && Array.isArray(json.coins)) {
// json.coins.map(c => { // json.coins.map(c => {
// return { // return {
// } // }
// }) // })
// } else { // } else {
// throw new Error(`Property "coins" is missing in appConfig.json`); // throw new Error(`Property "coins" is missing in appConfig.json`);
// } // }
// return { // return {
// coins: [], // coins: [],
@ -84,22 +83,21 @@ export const loadData = (): ThunkAction => {
export function loadTokensFromJSON(): AsyncAction { export function loadTokensFromJSON(): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
if (typeof window.localStorage === 'undefined') return; if (typeof window.localStorage === 'undefined') return;
try { try {
const config: Config = await httpRequest(AppConfigJSON, 'json'); const config: Config = await httpRequest(AppConfigJSON, 'json');
const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json'); const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json');
window.addEventListener('storage', event => { window.addEventListener('storage', (event) => {
dispatch( update(event) ); dispatch(update(event));
}) });
// load tokens // load tokens
const tokens = await config.coins.reduce(async (promise: Promise<TokensCollection>, coin: Coin): Promise<TokensCollection> => { const tokens = await config.coins.reduce(async (promise: Promise<TokensCollection>, coin: Coin): Promise<TokensCollection> => {
const collection: TokensCollection = await promise; const collection: TokensCollection = await promise;
const json = await httpRequest(coin.tokens, 'json'); const json = await httpRequest(coin.tokens, 'json');
collection[ coin.network ] = json; collection[coin.network] = json;
return collection; return collection;
}, Promise.resolve({})); }, Promise.resolve({}));
@ -107,62 +105,60 @@ export function loadTokensFromJSON(): AsyncAction {
if (devices) { if (devices) {
dispatch({ dispatch({
type: CONNECT.DEVICE_FROM_STORAGE, type: CONNECT.DEVICE_FROM_STORAGE,
payload: JSON.parse(devices) payload: JSON.parse(devices),
}) });
} }
const accounts: ?string = get('accounts'); const accounts: ?string = get('accounts');
if (accounts) { if (accounts) {
dispatch({ dispatch({
type: ACCOUNT.FROM_STORAGE, type: ACCOUNT.FROM_STORAGE,
payload: JSON.parse(accounts) payload: JSON.parse(accounts),
}) });
} }
const userTokens: ?string = get('tokens'); const userTokens: ?string = get('tokens');
if (userTokens) { if (userTokens) {
dispatch({ dispatch({
type: TOKEN.FROM_STORAGE, type: TOKEN.FROM_STORAGE,
payload: JSON.parse(userTokens) payload: JSON.parse(userTokens),
}) });
} }
const pending: ?string = get('pending'); const pending: ?string = get('pending');
if (pending) { if (pending) {
dispatch({ dispatch({
type: PENDING.FROM_STORAGE, type: PENDING.FROM_STORAGE,
payload: JSON.parse(pending) payload: JSON.parse(pending),
}) });
} }
const discovery: ?string = get('discovery'); const discovery: ?string = get('discovery');
if (discovery) { if (discovery) {
dispatch({ dispatch({
type: DISCOVERY.FROM_STORAGE, type: DISCOVERY.FROM_STORAGE,
payload: JSON.parse(discovery) payload: JSON.parse(discovery),
}) });
} }
dispatch({ dispatch({
type: STORAGE.READY, type: STORAGE.READY,
config, config,
tokens, tokens,
ERC20Abi ERC20Abi,
}) });
} catch (error) {
} catch(error) {
dispatch({ dispatch({
type: STORAGE.ERROR, type: STORAGE.ERROR,
error error,
}) });
} }
} };
} }
export function update(event: StorageEvent): AsyncAction { export function update(event: StorageEvent): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
if (!event.newValue) return; if (!event.newValue) return;
if (event.key === 'devices') { if (event.key === 'devices') {
@ -183,46 +179,44 @@ export function update(event: StorageEvent): AsyncAction {
if (event.key === 'accounts') { if (event.key === 'accounts') {
dispatch({ dispatch({
type: ACCOUNT.FROM_STORAGE, type: ACCOUNT.FROM_STORAGE,
payload: JSON.parse(event.newValue) payload: JSON.parse(event.newValue),
}); });
} }
if (event.key === 'tokens') { if (event.key === 'tokens') {
dispatch({ dispatch({
type: TOKEN.FROM_STORAGE, type: TOKEN.FROM_STORAGE,
payload: JSON.parse(event.newValue) payload: JSON.parse(event.newValue),
}); });
} }
if (event.key === 'pending') { if (event.key === 'pending') {
dispatch({ dispatch({
type: PENDING.FROM_STORAGE, type: PENDING.FROM_STORAGE,
payload: JSON.parse(event.newValue) payload: JSON.parse(event.newValue),
}); });
} }
if (event.key === 'discovery') { if (event.key === 'discovery') {
dispatch({ dispatch({
type: DISCOVERY.FROM_STORAGE, type: DISCOVERY.FROM_STORAGE,
payload: JSON.parse(event.newValue) payload: JSON.parse(event.newValue),
}); });
} }
} };
} }
export const save = (key: string, value: string): ThunkAction => { export const save = (key: string, value: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { if (typeof window.localStorage !== 'undefined') {
if (typeof window.localStorage !== 'undefined') { try {
try { window.localStorage.setItem(key, value);
window.localStorage.setItem(key, value); } catch (error) {
} catch (error) { // available = false;
// available = false; console.error(`Local Storage ERROR: ${error}`);
console.error("Local Storage ERROR: " + error)
}
} }
} }
} };
export const get = (key: string): ?string => { export const get = (key: string): ?string => {
try { try {
@ -231,4 +225,4 @@ export const get = (key: string): ?string => {
// available = false; // available = false;
return null; return null;
} }
} };

@ -1,9 +1,11 @@
/* @flow */ /* @flow */
'use strict';
import * as LOG from './constants/log'; 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'; import type { LogEntry } from '../reducers/LogReducer';
export type LogAction = { export type LogAction = {
@ -15,31 +17,26 @@ export type LogAction = {
payload: LogEntry payload: LogEntry
}; };
export const toggle = (): ThunkAction => { export const toggle = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { if (!getState().log.opened) {
window.scrollTo(0, 0);
if (!getState().log.opened) {
window.scrollTo(0, 0);
dispatch({ dispatch({
type: LOG.OPEN type: LOG.OPEN,
}); });
} else { } else {
dispatch({ dispatch({
type: LOG.CLOSE type: LOG.CLOSE,
}); });
}
} }
} };
// export const add = (type: string, message: string): Action => { // export const add = (type: string, message: string): Action => {
export const add = (type: string, message: any): Action => { export const add = (type: string, message: any): Action => ({
return { type: LOG.ADD,
type: LOG.ADD, payload: {
payload: { time: new Date().getTime(),
time: new Date().getTime(), type,
type, message,
message },
} });
}
}

@ -1,13 +1,15 @@
/* @flow */ /* @flow */
'use strict';
import TrezorConnect, { UI, UI_EVENT } from 'trezor-connect'; import TrezorConnect, { UI, UI_EVENT } from 'trezor-connect';
import type { Device } from 'trezor-connect';
import * as MODAL from './constants/modal'; import * as MODAL from './constants/modal';
import * as CONNECT from './constants/TrezorConnect'; 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 { State } from '../reducers/ModalReducer';
import type { Device } from 'trezor-connect';
export type ModalAction = { export type ModalAction = {
type: typeof MODAL.CLOSE type: typeof MODAL.CLOSE
@ -19,25 +21,23 @@ export type ModalAction = {
export const onPinSubmit = (value: string): Action => { export const onPinSubmit = (value: string): Action => {
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value }); TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
return { return {
type: MODAL.CLOSE type: MODAL.CLOSE,
} };
} };
export const onPassphraseSubmit = (passphrase: string): AsyncAction => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const resp = await TrezorConnect.uiResponse({
type: UI.RECEIVE_PASSPHRASE,
payload: {
value: passphrase,
save: true
}
});
dispatch({ export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
type: MODAL.CLOSE const resp = await TrezorConnect.uiResponse({
}); type: UI.RECEIVE_PASSPHRASE,
} payload: {
} value: passphrase,
save: true,
},
});
dispatch({
type: MODAL.CLOSE,
});
};
// export const askForRemember = (device: TrezorDevice): Action => { // export const askForRemember = (device: TrezorDevice): Action => {
// return { // return {
@ -46,88 +46,72 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => {
// } // }
// } // }
export const onRememberDevice = (device: TrezorDevice): Action => { export const onRememberDevice = (device: TrezorDevice): Action => ({
return { type: CONNECT.REMEMBER,
type: CONNECT.REMEMBER, device,
device });
}
}
export const onForgetDevice = (device: TrezorDevice): Action => { export const onForgetDevice = (device: TrezorDevice): Action => ({
return { type: CONNECT.FORGET,
type: CONNECT.FORGET, device,
device, });
}
}
export const onForgetSingleDevice = (device: TrezorDevice): Action => { export const onForgetSingleDevice = (device: TrezorDevice): Action => ({
return { type: CONNECT.FORGET_SINGLE,
type: CONNECT.FORGET_SINGLE, device,
device, });
}
}
export const onCancel = (): Action => { export const onCancel = (): Action => ({
return { type: MODAL.CLOSE,
type: MODAL.CLOSE });
}
} export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
dispatch(onCancel());
export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => { dispatch({
return (dispatch: Dispatch, getState: GetState): void => { 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({ dispatch({
type: CONNECT.DUPLICATE, type: CONNECT.FORGET,
device device: prevState.device,
}); });
} }
} };
export const onRememberRequest = (prevState: State): ThunkAction => { export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { // interrupt process of remembering device (force forget)
const state: State = getState().modal; // TODO: the same for disconnect more than 1 device at once
// handle case where forget modal is already opened const { modal } = getState();
// TODO: 2 modals at once (two devices disconnected in the same time) if (modal.opened && modal.windowType === CONNECT.REMEMBER_REQUEST) {
if (prevState.opened && prevState.windowType === CONNECT.REMEMBER_REQUEST) { if (device.features && modal.device && modal.device.features && modal.device.features.device_id === device.features.device_id) {
// forget current (new) dispatch({
if (state.opened) { type: MODAL.CLOSE,
dispatch({ });
type: CONNECT.FORGET, } else {
device: state.device
});
}
// forget previous (old)
dispatch({ dispatch({
type: CONNECT.FORGET, 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 { export default {
onPinSubmit, onPinSubmit,
@ -137,5 +121,5 @@ export default {
onForgetDevice, onForgetDevice,
onForgetSingleDevice, onForgetSingleDevice,
onCancel, onCancel,
onDuplicateDevice onDuplicateDevice,
} };

@ -1,9 +1,11 @@
/* @flow */ /* @flow */
'use strict';
import * as NOTIFICATION from './constants/notification'; 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'; import type { CallbackAction } from '../reducers/NotificationReducer';
export type NotificationAction = { export type NotificationAction = {
@ -23,31 +25,27 @@ export type NotificationAction = {
} }
} }
export const close = (payload: any = {}): Action => { export const close = (payload: any = {}): Action => ({
return { type: NOTIFICATION.CLOSE,
type: NOTIFICATION.CLOSE, payload,
payload });
}
}
// called from RouterService // called from RouterService
export const clear = (currentParams: RouterLocationState, requestedParams: RouterLocationState): AsyncAction => { export const clear = (currentParams: RouterLocationState, requestedParams: RouterLocationState): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { // if route has been changed from device view into something else (like other device, settings...)
// 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)
// 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) {
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) { const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string');
const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string'); entries.forEach((entry) => {
entries.forEach(entry => { if (typeof entry.devicePath === 'string') {
if (typeof entry.devicePath === 'string') { dispatch({
dispatch({ type: NOTIFICATION.CLOSE,
type: NOTIFICATION.CLOSE, payload: {
payload: { devicePath: entry.devicePath,
devicePath: entry.devicePath },
} });
}) }
} });
});
}
} }
} };

@ -1,8 +1,8 @@
/* @flow */ /* @flow */
'use strict';
import * as PENDING from './constants/pendingTx'; import * as PENDING from './constants/pendingTx';
import type { State, PendingTx } from '../reducers/PendingTxReducer' import type { State, PendingTx } from '../reducers/PendingTxReducer';
export type PendingTxAction = { export type PendingTxAction = {
type: typeof PENDING.FROM_STORAGE, type: typeof PENDING.FROM_STORAGE,

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import TrezorConnect from 'trezor-connect'; import TrezorConnect from 'trezor-connect';
import * as RECEIVE from './constants/receive'; import * as RECEIVE from './constants/receive';
@ -8,7 +8,9 @@ import * as NOTIFICATION from './constants/notification';
import { initialState } from '../reducers/ReceiveReducer'; import { initialState } from '../reducers/ReceiveReducer';
import type { State } 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 = { export type ReceiveAction = {
type: typeof RECEIVE.INIT, type: typeof RECEIVE.INIT,
@ -26,90 +28,80 @@ export type ReceiveAction = {
type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS
} }
export const init = (): ThunkAction => { export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const state: State = {
...initialState,
const state: State = { };
...initialState,
};
dispatch({ dispatch({
type: RECEIVE.INIT, type: RECEIVE.INIT,
state: state state,
}); });
} };
}
export const dispose = (): Action => { export const dispose = (): Action => ({
return { type: RECEIVE.DISPOSE,
type: RECEIVE.DISPOSE });
}
}
export const showUnverifiedAddress = (): Action => { export const showUnverifiedAddress = (): Action => ({
return { type: RECEIVE.SHOW_UNVERIFIED_ADDRESS,
type: RECEIVE.SHOW_UNVERIFIED_ADDRESS });
}
}
//export const showAddress = (address_n: string): AsyncAction => { //export const showAddress = (address_n: string): AsyncAction => {
export const showAddress = (address_n: Array<number>): AsyncAction => { export const showAddress = (address_n: Array<number>): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { 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; const response = await TrezorConnect.ethereumGetAddress({
if (!selected) return; device: {
path: selected.path,
instance: selected.instance,
state: selected.state,
},
path: address_n,
useEmptyPassphrase: !selected.instance,
});
if (selected && (!selected.connected || !selected.available)) { if (response && response.success) {
dispatch({ dispatch({
type: RECEIVE.REQUEST_UNVERIFIED, type: RECEIVE.SHOW_ADDRESS,
device: selected });
}); } else {
return; dispatch({
} type: RECEIVE.HIDE_ADDRESS,
});
const response = await TrezorConnect.ethereumGetAddress({ dispatch({
device: { type: NOTIFICATION.ADD,
path: selected.path, payload: {
instance: selected.instance, type: 'error',
state: selected.state 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 { export default {
init, init,
dispose, dispose,
showAddress, showAddress,
showUnverifiedAddress showUnverifiedAddress,
} };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import { LOCATION_CHANGE } from 'react-router-redux'; import { LOCATION_CHANGE } from 'react-router-redux';
import * as ACCOUNT from './constants/account'; import * as ACCOUNT from './constants/account';
@ -18,8 +18,8 @@ import type {
TrezorDevice, TrezorDevice,
AsyncAction, AsyncAction,
ThunkAction, ThunkAction,
Action, Action,
GetState, GetState,
Dispatch, Dispatch,
State, State,
} from '~/flowtype'; } from '~/flowtype';
@ -32,110 +32,101 @@ export type SelectedAccountAction = {
payload: $ElementType<State, 'selectedAccount'> payload: $ElementType<State, 'selectedAccount'>
}; };
export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => { export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { const locationChange: boolean = action.type === LOCATION_CHANGE;
const state: State = getState();
const locationChange: boolean = action.type === LOCATION_CHANGE; const location = state.router.location;
const state: State = getState(); const prevLocation = prevState.router.location;
const location = state.router.location;
const prevLocation = prevState.router.location;
let needUpdate: boolean = false; const needUpdate: boolean = false;
// reset form to default // reset form to default
if (action.type === SEND.TX_COMPLETE) { if (action.type === SEND.TX_COMPLETE) {
// dispatch( SendFormActions.init() ); // dispatch( SendFormActions.init() );
// linear action // linear action
// SessionStorageActions.clear(location.pathname); // SessionStorageActions.clear(location.pathname);
} }
// handle devices state change (from trezor-connect events or location change) // handle devices state change (from trezor-connect events or location change)
if (locationChange if (locationChange
|| prevState.accounts !== state.accounts || prevState.accounts !== state.accounts
|| prevState.discovery !== state.discovery || prevState.discovery !== state.discovery
|| prevState.tokens !== state.tokens || prevState.tokens !== state.tokens
|| prevState.pending !== state.pending || prevState.pending !== state.pending
|| prevState.web3 !== state.web3) { || 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<State, 'selectedAccount'> = {
// location: location.pathname,
account,
network,
discovery,
tokens,
pending,
web3,
};
if (locationChange) { let needUpdate: boolean = false;
// dispose current account view Object.keys(payload).forEach((key) => {
dispatch( dispose() ); if (Array.isArray(payload[key])) {
} if (Array.isArray(state.selectedAccount[key]) && payload[key].length !== state.selectedAccount[key].length) {
needUpdate = true;
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<State, 'selectedAccount'> = {
// 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;
}
} }
}) } else if (payload[key] !== state.selectedAccount[key]) {
needUpdate = true;
}
});
if (needUpdate) { if (needUpdate) {
dispatch({ dispatch({
type: ACCOUNT.UPDATE_SELECTED_ACCOUNT, type: ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload, payload,
}); });
// initialize SendFormReducer // initialize SendFormReducer
if (location.state.send && getState().sendForm.currency === "") { if (location.state.send && getState().sendForm.currency === '') {
dispatch( SendFormActions.init() ); dispatch(SendFormActions.init());
} }
if (location.state.send) { if (location.state.send) {
const rejectedTxs = pending.filter(tx => tx.rejected); const rejectedTxs = pending.filter(tx => tx.rejected);
rejectedTxs.forEach(tx => { rejectedTxs.forEach((tx) => {
dispatch({ dispatch({
type: NOTIFICATION.ADD, type: NOTIFICATION.ADD,
payload: { payload: {
type: "warning", type: 'warning',
title: "Pending transaction rejected", title: 'Pending transaction rejected',
message: `Transaction with id: ${ tx.id } not found.`, message: `Transaction with id: ${tx.id} not found.`,
cancelable: true, cancelable: true,
actions: [ actions: [
{ {
label: 'OK', label: 'OK',
callback: () => { callback: () => {
dispatch({ dispatch({
type: PENDING.TX_RESOLVED, type: PENDING.TX_RESOLVED,
tx, tx,
}); });
} },
} },
] ],
} },
})
}); });
} });
} }
} }
} }
} };
export const dispose = (): Action => { export const dispose = (): Action => ({
return { type: ACCOUNT.DISPOSE,
type: ACCOUNT.DISPOSE });
}
}

File diff suppressed because it is too large Load Diff

@ -1,33 +1,30 @@
/* @flow */ /* @flow */
'use strict';
import type { State as SendFormState } from '../reducers/SendFormReducer'; import type { State as SendFormState } from '../reducers/SendFormReducer';
import type { import type {
ThunkAction, ThunkAction,
GetState, GetState,
Dispatch, Dispatch,
} from '~/flowtype'; } from '~/flowtype';
const PREFIX: string = 'trezor:draft-tx:'; const PREFIX: string = 'trezor:draft-tx:';
export const save = (): ThunkAction => { export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { if (typeof window.localStorage === 'undefined') return;
if (typeof window.localStorage === 'undefined') return;
const location = getState().router.location.pathname;
const location = getState().router.location.pathname; const state = getState().sendForm;
const state = getState().sendForm; if (!state.untouched) {
if (!state.untouched) { try {
try { window.sessionStorage.setItem(`${PREFIX}${location}`, JSON.stringify(state));
window.sessionStorage.setItem(`${PREFIX}${location}`, JSON.stringify(state) ); } catch (error) {
} catch (error) { console.error(`Saving sessionStorage error: ${error}`);
console.error("Saving sessionStorage error: " + error)
}
} }
} }
} };
export const load = (location: string): ?SendFormState => { export const load = (location: string): ?SendFormState => {
if (typeof window.localStorage === 'undefined') return; if (typeof window.localStorage === 'undefined') return;
try { try {
@ -39,20 +36,16 @@ export const load = (location: string): ?SendFormState => {
} }
return state; return state;
} catch (error) { } catch (error) {
console.error("Loading sessionStorage error: " + error) console.error(`Loading sessionStorage error: ${error}`);
} }
};
return; export const clear = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
} if (typeof window.localStorage === 'undefined') return;
const location = getState().router.location.pathname;
export const clear = (): ThunkAction => { try {
return (dispatch: Dispatch, getState: GetState): void => { window.sessionStorage.removeItem(`${PREFIX}${location}`);
if (typeof window.localStorage === 'undefined') return; } catch (error) {
const location = getState().router.location.pathname; console.error(`Clearing sessionStorage error: ${error}`);
try {
window.sessionStorage.removeItem(`${PREFIX}${location}`);
} catch (error) {
console.error("Clearing sessionStorage error: " + error)
}
} }
} };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import EthereumjsUtil from 'ethereumjs-util'; import EthereumjsUtil from 'ethereumjs-util';
import * as SUMMARY from './constants/summary'; import * as SUMMARY from './constants/summary';
@ -7,7 +7,9 @@ import * as TOKEN from './constants/token';
import { resolveAfter } from '../utils/promiseUtils'; import { resolveAfter } from '../utils/promiseUtils';
import { initialState } from '../reducers/SummaryReducer'; 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 { State } from '../reducers/SummaryReducer';
import type { Token } from '../reducers/TokensReducer'; import type { Token } from '../reducers/TokensReducer';
@ -20,29 +22,21 @@ export type SummaryAction = {
type: typeof SUMMARY.DETAILS_TOGGLE type: typeof SUMMARY.DETAILS_TOGGLE
} }
export const init = (): ThunkAction => { export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const state: State = {
...initialState,
const state: State = { };
...initialState,
};
dispatch({
type: SUMMARY.INIT,
state: state
});
}
}
export const dispose = (): Action => { dispatch({
return { type: SUMMARY.INIT,
type: SUMMARY.DISPOSE state,
} });
} };
export const onDetailsToggle = (): Action => { export const dispose = (): Action => ({
return { type: SUMMARY.DISPOSE,
type: SUMMARY.DETAILS_TOGGLE });
}
}
export const onDetailsToggle = (): Action => ({
type: SUMMARY.DETAILS_TOGGLE,
});

@ -1,10 +1,12 @@
/* @flow */ /* @flow */
'use strict';
import * as TOKEN from './constants/token'; import * as TOKEN from './constants/token';
import { getTokenInfoAsync, getTokenBalanceAsync } from './Web3Actions'; 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 { State, Token } from '../reducers/TokensReducer';
import type { Account } from '../reducers/AccountsReducer'; import type { Account } from '../reducers/AccountsReducer';
import type { NetworkToken } from '../reducers/LocalStorageReducer'; import type { NetworkToken } from '../reducers/LocalStorageReducer';
@ -29,86 +31,71 @@ type SelectOptions = {
// action from component <reactSelect> // action from component <reactSelect>
export const load = (input: string, network: string): AsyncAction => { export const load = (input: string, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<any> => {
return async (dispatch: Dispatch, getState: GetState): Promise<any> => { if (input.length < 1) return;
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 add = (token: NetworkToken, account: Account): AsyncAction => { const tokens = getState().localStorage.tokens[network];
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { const value = input.toLowerCase();
const result = tokens.filter(t => t.symbol.toLowerCase().indexOf(value) >= 0
const web3instance = getState().web3.find(w3 => w3.network === account.network); || t.address.toLowerCase().indexOf(value) >= 0
if (!web3instance) return; || t.name.toLowerCase().indexOf(value) >= 0);
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 => { if (result.length > 0) {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return { options: result };
const newState: Array<Token> = [ ...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
})
} }
} const web3instance = getState().web3.find(w3 => w3.network === network);
if (!web3instance) return;
export const remove = (token: Token): Action => {
return { const info = await getTokenInfoAsync(web3instance.erc20, input);
type: TOKEN.REMOVE, if (info) {
token return {
options: [info],
};
}
//await resolveAfter(300000);
//await resolveAfter(3000);
};
export const add = (token: NetworkToken, account: Account): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
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<void> => {
const newState: Array<Token> = [...getState().tokens];
const 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,
});
};
export const remove = (token: Token): Action => ({
type: TOKEN.REMOVE,
token,
});

@ -1,7 +1,9 @@
/* @flow */ /* @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 TOKEN from './constants/token';
import * as CONNECT from './constants/TrezorConnect'; import * as CONNECT from './constants/TrezorConnect';
import * as NOTIFICATION from './constants/notification'; import * as NOTIFICATION from './constants/notification';
@ -20,17 +22,17 @@ import type {
TransportMessage, TransportMessage,
DeviceMessageType, DeviceMessageType,
TransportMessageType, TransportMessageType,
UiMessageType UiMessageType,
} from 'trezor-connect'; } from 'trezor-connect';
import type { import type {
Dispatch, Dispatch,
GetState, GetState,
Action, Action,
ThunkAction, ThunkAction,
AsyncAction, AsyncAction,
TrezorDevice, TrezorDevice,
RouterLocationState RouterLocationState,
} from '~/flowtype'; } from '~/flowtype';
@ -81,301 +83,276 @@ export type TrezorConnectAction = {
}; };
export const init = (): AsyncAction => { export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { // set listeners
// set listeners TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => { // post event to reducers
// post event to reducers const type: DeviceMessageType = event.type; // assert flow type
const type: DeviceMessageType = event.type; // assert flow type dispatch({
dispatch({ type,
type, device: event.payload,
device: event.payload
});
}); });
});
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => { TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
// post event to reducers // post event to reducers
const type: UiMessageType = event.type; // assert flow type const type: UiMessageType = event.type; // assert flow type
dispatch({ dispatch({
type, type,
payload: event.payload payload: event.payload,
});
}); });
});
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => { TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
// post event to reducers // post event to reducers
const type: TransportMessageType = event.type; // assert flow type const type: TransportMessageType = event.type; // assert flow type
dispatch({ dispatch({
type, type,
payload: event.payload payload: event.payload,
});
}); });
});
try { try {
await TrezorConnect.init({ await TrezorConnect.init({
transportReconnect: true, transportReconnect: true,
// connectSrc: 'https://localhost:8088/', // connectSrc: 'https://localhost:8088/',
connectSrc: 'https://sisyfos.trezor.io/', connectSrc: 'https://sisyfos.trezor.io/',
debug: false, debug: false,
popup: false, popup: false,
webusb: true, webusb: true,
pendingTransportEvent: (getState().devices.length < 1) pendingTransportEvent: (getState().devices.length < 1),
}); });
} catch (error) { } catch (error) {
// dispatch({ // dispatch({
// type: CONNECT.INITIALIZATION_ERROR, // type: CONNECT.INITIALIZATION_ERROR,
// error // error
// }) // })
}
} }
} };
// called after backend was initialized // called after backend was initialized
// set listeners for connect/disconnect // set listeners for connect/disconnect
export const postInit = (): ThunkAction => { export const postInit = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const handleDeviceConnect = (device: Device) => {
dispatch(initConnectedDevice(device));
};
const handleDeviceConnect = (device: Device) => { TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect);
dispatch( initConnectedDevice(device) ); TrezorConnect.off(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
}
TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect); TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
TrezorConnect.off(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect); TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect); const { devices } = getState();
TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
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) { if (devices.length > 0) {
dispatch({ const unacquired: ?TrezorDevice = devices.find(d => d.unacquired);
type: WALLET.SET_INITIAL_URL, if (unacquired) {
// pathname: null, dispatch(onSelectDevice(unacquired));
// params: null } else {
}); const latest: Array<TrezorDevice> = 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<TrezorDevice> = 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<TrezorDevice>): Array<TrezorDevice> => {
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 => { const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => devices.sort((a, b) => {
return (dispatch: Dispatch, getState: GetState): void => { if (!a.ts || !b.ts) {
const selected = getState().wallet.selectedDevice; return -1;
// 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) );
// }
} }
} 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 // selection from Aside dropdown button
// after device_connect event // after device_connect event
// or after acquiring device // or after acquiring device
// device type could be local TrezorDevice or Device (from trezor-connect device_connect event) // device type could be local TrezorDevice or Device (from trezor-connect device_connect event)
export const onSelectDevice = (device: TrezorDevice | Device): ThunkAction => { export const onSelectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { // || device.isUsedElsewhere
// || device.isUsedElsewhere
// switch to initial url and reset this value
// switch to initial url and reset this value
if (!device.features) {
if (!device.features) { dispatch(push(`/device/${device.path}/acquire`));
dispatch( push(`/device/${ device.path }/acquire`) ); } else if (device.features.bootloader_mode) {
} else if (device.features.bootloader_mode) { dispatch(push(`/device/${device.path}/bootloader`));
dispatch( push(`/device/${ device.path }/bootloader`) ); } else if (!device.features.initialized) {
} else if (!device.features.initialized) { dispatch(push(`/device/${device.features.device_id}/initialize`));
dispatch( push(`/device/${ device.features.device_id }/initialize`) ); } else if (typeof device.instance === 'number') {
} else if (typeof device.instance === 'number') { dispatch(push(`/device/${device.features.device_id}:${device.instance}`));
dispatch( push(`/device/${ device.features.device_id }:${ device.instance }`) ); } else {
} else { const deviceId: string = device.features.device_id;
const urlParams: RouterLocationState = getState().router.location.state;
const deviceId: string = device.features.device_id; // let url: string = `/device/${ device.features.device_id }/network/ethereum/account/0`;
const urlParams: RouterLocationState = getState().router.location.state; let url: string = `/device/${deviceId}`;
// let url: string = `/device/${ device.features.device_id }/network/ethereum/account/0`; let instance: ?number;
let url: string = `/device/${ deviceId }`; // check if device is not TrezorDevice type
let instance: ?number; if (!device.hasOwnProperty('ts')) {
// check if device is not TrezorDevice type // its device from trezor-connect (called in initConnectedDevice triggered by device_connect event)
if (!device.hasOwnProperty('ts')) { // need to lookup if there are unavailable instances
// its device from trezor-connect (called in initConnectedDevice triggered by device_connect event) const available: Array<TrezorDevice> = getState().devices.filter(d => d.path === device.path);
// need to lookup if there are unavailable instances const latest: Array<TrezorDevice> = sortDevices(available);
const available: Array<TrezorDevice> = getState().devices.filter(d => d.path === device.path);
const latest: Array<TrezorDevice> = sortDevices(available); if (latest.length > 0 && latest[0].instance) {
url += `:${latest[0].instance}`;
if (latest.length > 0 && latest[0].instance) { instance = 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) { if (urlParams.deviceInstance !== instance || urlParams.device !== deviceId) {
dispatch( push(url) ); dispatch(push(url));
}
} }
} }
} };
export const switchToFirstAvailableDevice = (): AsyncAction => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { devices } = getState(); export const switchToFirstAvailableDevice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
if (devices.length > 0) { const { devices } = getState();
// TODO: Priority: if (devices.length > 0) {
// 1. First Unacquired // TODO: Priority:
// 2. First connected // 1. First Unacquired
// 3. Saved with latest timestamp // 2. First connected
const unacquired = devices.find(d => d.unacquired); // 3. Saved with latest timestamp
if (unacquired) { const unacquired = devices.find(d => d.unacquired);
dispatch( initConnectedDevice(unacquired) ); if (unacquired) {
} else { dispatch(initConnectedDevice(unacquired));
const latest: Array<TrezorDevice> = sortDevices(devices);
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
dispatch( onSelectDevice(firstConnected || latest[0]) );
}
} else { } else {
dispatch( push('/') ); const latest: Array<TrezorDevice> = sortDevices(devices);
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
dispatch(onSelectDevice(firstConnected || latest[0]));
} }
} else {
dispatch(push('/'));
} }
} };
export const getSelectedDeviceState = (): AsyncAction => { export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { const selected = getState().wallet.selectedDevice;
const selected = getState().wallet.selectedDevice; if (selected
if (selected
&& selected.connected && selected.connected
&& (selected.features && !selected.features.bootloader_mode && selected.features.initialized) && (selected.features && !selected.features.bootloader_mode && selected.features.initialized)
&& !selected.state) { && !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({ if (response && response.success) {
device: { dispatch({
path: selected.path, type: CONNECT.AUTH_DEVICE,
instance: selected.instance, device: selected,
state: selected.state 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 => { export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { 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) {
if (device && device.features) { dispatch(DiscoveryActions.stop(selected));
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); const instances = getState().devices.filter(d => d.features && d.state && !d.remember && d.features.device_id === device.features.device_id);
if (instances.length > 0) { if (instances.length > 0) {
dispatch({ dispatch({
type: CONNECT.REMEMBER_REQUEST, type: CONNECT.REMEMBER_REQUEST,
device: instances[0], device: instances[0],
instances, instances,
}); });
}
} }
} }
} };
export const coinChanged = (network: ?string): ThunkAction => { export const coinChanged = (network: ?string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const selected: ?TrezorDevice = getState().wallet.selectedDevice;
const selected: ?TrezorDevice = getState().wallet.selectedDevice; if (!selected) return;
if (!selected) return;
dispatch( DiscoveryActions.stop(selected) ); dispatch(DiscoveryActions.stop(selected));
if (network) { if (network) {
dispatch( DiscoveryActions.start(selected, network) ); dispatch(DiscoveryActions.start(selected, network));
}
} }
} };
export function reload(): AsyncAction { export function reload(): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
} };
} }
export function acquire(): AsyncAction { export function acquire(): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const selected: ?TrezorDevice = getState().wallet.selectedDevice; const selected: ?TrezorDevice = getState().wallet.selectedDevice;
if (!selected) return; if (!selected) return;
dispatch({ dispatch({
type: CONNECT.START_ACQUIRING, type: CONNECT.START_ACQUIRING,
}) });
const response = await TrezorConnect.getFeatures({ const response = await TrezorConnect.getFeatures({
device: { device: {
path: selected.path, path: selected.path,
}, },
@ -398,47 +375,41 @@ export function acquire(): AsyncAction {
// } // }
// } // }
// ] // ]
} },
}) });
} }
dispatch({ dispatch({
type: CONNECT.STOP_ACQUIRING, type: CONNECT.STOP_ACQUIRING,
}) });
} };
} }
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => { export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { if (device.features) {
if (device.features) { const devUrl: string = `${device.features.device_id}${device.instance ? `:${device.instance}` : ''}`;
const devUrl: string = `${device.features.device_id}${ device.instance ? `:${ device.instance}` : '' }`; dispatch(push(`/device/${devUrl}/settings`));
dispatch( push( `/device/${ devUrl }/settings` ) );
}
} }
} };
// called from Aside - device menu (forget single instance) // called from Aside - device menu (forget single instance)
export const forget = (device: TrezorDevice): Action => { export const forget = (device: TrezorDevice): Action => ({
return { type: CONNECT.FORGET_REQUEST,
type: CONNECT.FORGET_REQUEST, device,
device });
};
} export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
dispatch({
export const duplicateDevice = (device: TrezorDevice): AsyncAction => { type: CONNECT.TRY_TO_DUPLICATE,
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { device,
dispatch({ });
type: CONNECT.TRY_TO_DUPLICATE, };
device
})
}
}
export function addAccount(): ThunkAction { export function addAccount(): ThunkAction {
return (dispatch: Dispatch, getState: GetState): void => { return (dispatch: Dispatch, getState: GetState): void => {
const selected = getState().wallet.selectedDevice; const selected = getState().wallet.selectedDevice;
if (!selected) return; 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
} };
} }

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import { LOCATION_CHANGE } from 'react-router-redux'; import { LOCATION_CHANGE } from 'react-router-redux';
import * as WALLET from './constants/wallet'; import * as WALLET from './constants/wallet';
@ -8,20 +8,20 @@ import * as stateUtils from '../reducers/utils';
import type { Device } from 'trezor-connect'; import type { Device } from 'trezor-connect';
import type import type
{ {
Account, Account,
Coin, Coin,
Discovery, Discovery,
Token, Token,
Web3Instance, Web3Instance,
TrezorDevice, TrezorDevice,
RouterLocationState, RouterLocationState,
ThunkAction, ThunkAction,
AsyncAction, AsyncAction,
Action, Action,
Dispatch, Dispatch,
GetState, GetState,
State State,
} from '~/flowtype'; } from '~/flowtype';
export type WalletAction = { export type WalletAction = {
@ -47,82 +47,66 @@ export type WalletAction = {
devices: Array<TrezorDevice> devices: Array<TrezorDevice>
} }
export const init = (): ThunkAction => { export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { const updateOnlineStatus = (event) => {
dispatch({
const updateOnlineStatus = (event) => { type: WALLET.ONLINE_STATUS,
dispatch({ online: navigator.onLine,
type: WALLET.ONLINE_STATUS, });
online: navigator.onLine };
}) window.addEventListener('online', updateOnlineStatus);
} window.addEventListener('offline', updateOnlineStatus);
window.addEventListener('online', updateOnlineStatus); };
window.addEventListener('offline', updateOnlineStatus);
} export const onBeforeUnload = (): WalletAction => ({
} type: WALLET.ON_BEFORE_UNLOAD,
});
export const onBeforeUnload = (): WalletAction => {
return { export const toggleDeviceDropdown = (opened: boolean): WalletAction => ({
type: WALLET.ON_BEFORE_UNLOAD type: WALLET.TOGGLE_DEVICE_DROPDOWN,
} opened,
} });
export const toggleDeviceDropdown = (opened: boolean): WalletAction => {
return {
type: WALLET.TOGGLE_DEVICE_DROPDOWN,
opened
}
}
// This method will be called after each DEVICE.CONNECT action // This method will be called after each DEVICE.CONNECT action
// if connected device has different "passphrase_protection" settings than saved instances // if connected device has different "passphrase_protection" settings than saved instances
// all saved instances will be removed immediately inside DevicesReducer // all saved instances will be removed immediately inside DevicesReducer
// This method will clear leftovers associated with removed instances from reducers. // This method will clear leftovers associated with removed instances from reducers.
// (DiscoveryReducer, AccountReducer, TokensReducer) // (DiscoveryReducer, AccountReducer, TokensReducer)
export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => { export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
return (dispatch: Dispatch, getState: GetState): void => { if (!device.features) return;
if (!device.features) return;
const affectedDevices = prevState.devices.filter(d => const affectedDevices = prevState.devices.filter(d => d.features
d.features
&& d.features.device_id === device.features.device_id && 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) { if (affectedDevices.length > 0) {
dispatch({ dispatch({
type: WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA, type: WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
devices: affectedDevices, devices: affectedDevices,
}) });
}
} }
} };
export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => { export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { const locationChange: boolean = action.type === LOCATION_CHANGE;
const state: State = getState();
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) {
// handle devices state change (from trezor-connect events or location change) const device = stateUtils.getSelectedDevice(state);
if (locationChange || prevState.devices !== state.devices) { if (state.wallet.selectedDevice !== device) {
const device = stateUtils.getSelectedDevice(state); if (device && stateUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
if (state.wallet.selectedDevice !== device) { dispatch({
if (device && stateUtils.isSelectedDevice(state.wallet.selectedDevice, device)) { type: WALLET.UPDATE_SELECTED_DEVICE,
dispatch({ device,
type: WALLET.UPDATE_SELECTED_DEVICE, });
device } else {
}); dispatch({
} else { type: WALLET.SET_SELECTED_DEVICE,
dispatch({ device,
type: WALLET.SET_SELECTED_DEVICE, });
device
});
}
} }
} }
} }
} };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import Web3 from 'web3'; import Web3 from 'web3';
import HDKey from 'hdkey'; import HDKey from 'hdkey';
@ -7,27 +7,27 @@ import HDKey from 'hdkey';
import EthereumjsUtil from 'ethereumjs-util'; import EthereumjsUtil from 'ethereumjs-util';
import EthereumjsTx from 'ethereumjs-tx'; import EthereumjsTx from 'ethereumjs-tx';
import TrezorConnect from 'trezor-connect'; 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 { strip } from '../utils/ethUtils';
import * as WEB3 from './constants/web3'; import * as WEB3 from './constants/web3';
import * as PENDING from './constants/pendingTx'; import * as PENDING from './constants/pendingTx';
import * as AccountsActions from '../actions/AccountsActions'; import * as AccountsActions from './AccountsActions';
import * as TokenActions from '../actions/TokenActions'; import * as TokenActions from './TokenActions';
import type { import type {
Dispatch, Dispatch,
GetState, GetState,
Action, Action,
AsyncAction, AsyncAction,
} from '~/flowtype'; } from '~/flowtype';
import type { ContractFactory, EstimateGasOptions } from 'web3';
import type BigNumber from 'bignumber.js';
import type { Account } from '../reducers/AccountsReducer'; import type { Account } from '../reducers/AccountsReducer';
import type { PendingTx } from '../reducers/PendingTxReducer'; import type { PendingTx } from '../reducers/PendingTxReducer';
import type { Web3Instance } from '../reducers/Web3Reducer'; import type { Web3Instance } from '../reducers/Web3Reducer';
import type { Token } from '../reducers/TokensReducer'; import type { Token } from '../reducers/TokensReducer';
import type { NetworkToken } from '../reducers/LocalStorageReducer'; import type { NetworkToken } from '../reducers/LocalStorageReducer';
import type { TransactionStatus, TransactionReceipt } from 'web3';
export type Web3Action = { export type Web3Action = {
type: typeof WEB3.READY, type: typeof WEB3.READY,
@ -53,10 +53,9 @@ export type Web3UpdateGasPriceAction = {
export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { config, ERC20Abi } = getState().localStorage; const { config, ERC20Abi } = getState().localStorage;
const coin = config.coins[ coinIndex ]; const coin = config.coins[coinIndex];
if (!coin) { if (!coin) {
// all instances done // all instances done
dispatch({ dispatch({
@ -72,12 +71,12 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
if (instance) { if (instance) {
const currentHost = instance.currentProvider.host; const currentHost = instance.currentProvider.host;
let currentHostIndex: number = urls.indexOf(currentHost); const currentHostIndex: number = urls.indexOf(currentHost);
if (currentHostIndex + 1 < urls.length) { if (currentHostIndex + 1 < urls.length) {
web3host = urls[currentHostIndex + 1]; web3host = urls[currentHostIndex + 1];
} else { } else {
console.error("TODO: Backend " + network + " not working", instance.currentProvider ); console.error(`TODO: Backend ${network} not working`, instance.currentProvider);
dispatch({ dispatch({
type: WEB3.CREATE, type: WEB3.CREATE,
@ -88,17 +87,17 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
erc20: instance.eth.contract(ERC20Abi), erc20: instance.eth.contract(ERC20Abi),
latestBlock: '0', latestBlock: '0',
gasPrice: '0', gasPrice: '0',
} },
}); });
// try next coin // try next coin
dispatch( init(null, coinIndex + 1) ); dispatch(init(null, coinIndex + 1));
return; return;
} }
} }
//const instance = new Web3(window.web3.currentProvider); //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://pyrus2.ubiqscan.io') ); // UBQ
//instance = new Web3( new Web3.providers.HttpProvider('https://node.expanse.tech/') ); // EXP //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 // initial check if backend is running
if (!web3.currentProvider.isConnected()) { if (!web3.currentProvider.isConnected()) {
// try different url // try different url
dispatch( init(web3, coinIndex) ); dispatch(init(web3, coinIndex));
return; return;
} }
@ -122,12 +121,12 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
type: WEB3.CREATE, type: WEB3.CREATE,
instance: { instance: {
network, network,
web3: web3, web3,
chainId: coin.chainId, chainId: coin.chainId,
erc20, erc20,
latestBlock: '0', latestBlock: '0',
gasPrice: '0', gasPrice: '0',
} },
}); });
// dispatch({ // dispatch({
@ -136,15 +135,13 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
// gasPrice // gasPrice
// }); // });
// console.log("GET CHAIN", instance.version.network) // console.log("GET CHAIN", instance.version.network)
// instance.version.getWhisper((err, shh) => { // instance.version.getWhisper((err, shh) => {
// console.log("-----whisperrr", error, shh) // console.log("-----whisperrr", error, shh)
// }) // })
// const sshFilter = instance.ssh.filter('latest'); // const sshFilter = instance.ssh.filter('latest');
// sshFilter.watch((error, blockHash) => { // sshFilter.watch((error, blockHash) => {
@ -157,10 +154,9 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
const onBlockMined = async (error: ?Error, blockHash: ?string) => { const onBlockMined = async (error: ?Error, blockHash: ?string) => {
if (error) { if (error) {
window.setTimeout(() => { window.setTimeout(() => {
// try again // try again
onBlockMined(new Error("manually_triggered_error"), undefined); onBlockMined(new Error('manually_triggered_error'), undefined);
}, 30000); }, 30000);
} }
@ -168,7 +164,7 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
dispatch({ dispatch({
type: WEB3.BLOCK_UPDATED, type: WEB3.BLOCK_UPDATED,
network, network,
blockHash blockHash,
}); });
} }
@ -181,70 +177,66 @@ export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
// dispatch( getBalance(account) ); // dispatch( getBalance(account) );
// TODO: check if nonce was updated, // TODO: check if nonce was updated,
// update tokens balance, // update tokens balance,
// update account balance, // update account balance,
// update pending transactions // update pending transactions
} }
dispatch( getBalance(account) ); dispatch(getBalance(account));
// dispatch( getNonce(account) ); // dispatch( getNonce(account) );
} }
const tokens = getState().tokens.filter(t => t.network === network); const tokens = getState().tokens.filter(t => t.network === network);
for (const token of tokens) { 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); const pending = getState().pending.filter(p => p.network === network);
for (const tx of pending) { for (const tx of pending) {
dispatch( getTransactionReceipt(tx) ); dispatch(getTransactionReceipt(tx));
} }
};
}
// latestBlockFilter.watch(onBlockMined); // latestBlockFilter.watch(onBlockMined);
onBlockMined(new Error("manually_triggered_error"), undefined); onBlockMined(new Error('manually_triggered_error'), undefined);
// init next coin // 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') ); // let instance2 = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') );
// console.log("INIT WEB3", instance, instance2); // console.log("INIT WEB3", instance, instance2);
// instance2.eth.getGasPrice((error, gasPrice) => { // instance2.eth.getGasPrice((error, gasPrice) => {
// console.log("---gasss price from UBQ", gasPrice) // console.log("---gasss price from UBQ", gasPrice)
// }); // });
} };
} }
export function getGasPrice(network: string): AsyncAction { export function getGasPrice(network: string): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const index: number = getState().web3.findIndex(w3 => w3.network === network); const index: number = getState().web3.findIndex(w3 => w3.network === network);
const web3instance = getState().web3[ index ]; const web3instance = getState().web3[index];
const web3 = web3instance.web3; const web3 = web3instance.web3;
web3.eth.getGasPrice((error, gasPrice) => { web3.eth.getGasPrice((error, gasPrice) => {
if (!error) { if (!error) {
if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) { if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) {
dispatch({ dispatch({
type: WEB3.GAS_PRICE_UPDATED, type: WEB3.GAS_PRICE_UPDATED,
network: network, network,
gasPrice gasPrice,
}); });
} }
} }
}); });
} };
} }
export function getBalance(account: Account): AsyncAction { export function getBalance(account: Account): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0]; const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0];
const web3: Web3 = web3instance.web3; const web3: Web3 = web3instance.web3;
@ -256,42 +248,39 @@ export function getBalance(account: Account): AsyncAction {
account.address, account.address,
account.network, account.network,
account.deviceState, account.deviceState,
newBalance newBalance,
)); ));
// dispatch( loadHistory(addr) ); // dispatch( loadHistory(addr) );
} }
} }
}); });
} };
} }
export function getTokenBalance(token: Token): AsyncAction { export function getTokenBalance(token: Token): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.filter(w3 => w3.network === token.network)[0]; const web3instance = getState().web3.filter(w3 => w3.network === token.network)[0];
const web3 = web3instance.web3; const web3 = web3instance.web3;
const contract = web3instance.erc20.at(token.address); const contract = web3instance.erc20.at(token.address);
contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => { contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => {
if (balance) { 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) { if (newBalance !== token.balance) {
dispatch(TokenActions.setBalance( dispatch(TokenActions.setBalance(
token.address, token.address,
token.ethAddress, token.ethAddress,
newBalance newBalance,
)); ));
} }
} }
}); });
} };
} }
export function getNonce(account: Account): AsyncAction { export function getNonce(account: Account): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0]; const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0];
const web3 = web3instance.web3; const web3 = web3instance.web3;
@ -302,158 +291,138 @@ export function getNonce(account: Account): AsyncAction {
} }
} }
}); });
} };
} }
export const getTransactionReceipt = (tx: PendingTx): AsyncAction => { export const getTransactionReceipt = (tx: PendingTx): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => { 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]; web3.eth.getTransaction(tx.id, (error: Error, status: TransactionStatus) => {
const web3 = web3instance.web3; if (!error && !status) {
dispatch({
web3.eth.getTransaction(tx.id, (error: Error, status: TransactionStatus) => { type: PENDING.TX_NOT_FOUND,
if (!error && !status) { tx,
dispatch({ });
type: PENDING.TX_NOT_FOUND, } else if (status && status.blockNumber) {
tx, web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => {
}) if (receipt) {
} else if (status && status.blockNumber) { if (status.gas !== receipt.gasUsed) {
web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => {
if (receipt) {
if (status.gas !== receipt.gasUsed) {
dispatch({
type: PENDING.TX_TOKEN_ERROR,
tx
})
}
dispatch({ dispatch({
type: PENDING.TX_RESOLVED, type: PENDING.TX_TOKEN_ERROR,
tx, tx,
receipt });
})
} }
dispatch({
}); type: PENDING.TX_RESOLVED,
} tx,
}); receipt,
} });
} }
});
export const getTransaction = (web3: Web3, txid: string): Promise<any> => { }
return new Promise((resolve, reject) => {
web3.eth.getTransaction(txid, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
}); });
} };
export const getBalanceAsync = (web3: Web3, address: string): Promise<BigNumber> => { export const getTransaction = (web3: Web3, txid: string): Promise<any> => new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { web3.eth.getTransaction(txid, (error, result) => {
web3.eth.getBalance(address, (error: Error, result: BigNumber) => { if (error) {
if (error) { reject(error);
reject(error); } else {
} else { resolve(result);
resolve(result); }
}
});
}); });
} });
export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise<string> => { export const getBalanceAsync = (web3: Web3, address: string): Promise<BigNumber> => new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { web3.eth.getBalance(address, (error: Error, result: BigNumber) => {
if (error) {
const contract = erc20.at(token.address); reject(error);
contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => { } else {
if (error) { resolve(result);
reject(error); }
} else {
const newBalance: string = balance.dividedBy( Math.pow(10, token.decimals) ).toString(10);
resolve(newBalance);
}
});
}); });
} });
export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise<string> => 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<number> => 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<number> => {
return new Promise((resolve, reject) => { export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise<?NetworkToken> => new Promise((resolve, reject) => {
web3.eth.getTransactionCount(address, (error: Error, result: number) => { const contract = erc20.at(address, (error, res) => {
if (error) { // console.warn("callback", error, res)
reject(error);
} else {
resolve(result);
}
});
}); });
}
const info: NetworkToken = {
address,
name: '',
symbol: '',
decimals: 0,
};
export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise<?NetworkToken> => { contract.name.call((error: Error, name: string) => {
return new Promise((resolve, reject) => { if (error) {
resolve(null);
return;
}
info.name = name;
const contract = erc20.at(address, (error, res) => {
// console.warn("callback", error, res)
});
const info: NetworkToken = { contract.symbol.call((error: Error, symbol: string) => {
address,
name: '',
symbol: '',
decimals: 0
};
contract.name.call((error: Error, name: string) => {
if (error) { if (error) {
resolve(null); resolve(null);
return; return;
} else {
info.name = name;
} }
info.symbol = symbol;
contract.symbol.call((error: Error, symbol: string) => {
if (error) {
resolve(null); contract.decimals.call((error: Error, decimals: BigNumber) => {
return; if (decimals) {
info.decimals = decimals.toNumber();
resolve(info);
} else { } 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<number> => { export const estimateGas = (web3: Web3, options: EstimateGasOptions): Promise<number> => new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { web3.eth.estimateGas(options, (error: ?Error, gas: ?number) => {
web3.eth.estimateGas(options, (error: ?Error, gas: ?number) => { if (error) {
if (error) { reject(error);
reject(error); } else if (typeof gas === 'number') {
} else if (typeof gas === 'number'){ resolve(gas);
resolve(gas); }
} });
}); });
})
} export const pushTx = (web3: Web3, tx: any): Promise<string> => new Promise((resolve, reject) => {
web3.eth.sendRawTransaction(tx, (error: Error, result: string) => {
export const pushTx = (web3: Web3, tx: any): Promise<string> => { if (error) {
return new Promise((resolve, reject) => { reject(error);
web3.eth.sendRawTransaction(tx, (error: Error, result: string) => { } else {
if (error) { resolve(result);
reject(error); }
} else { });
resolve(result); });
}
});
})
}

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
//regExp1 : string = '(.*)' //regExp1 : string = '(.*)'
//regExp2 : '$1' = '$1' //regExp2 : '$1' = '$1'

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const INIT: 'account__init' = 'account__init'; export const INIT: 'account__init' = 'account__init';
export const DISPOSE: 'account__dispose' = 'account__dispose'; export const DISPOSE: 'account__dispose' = 'account__dispose';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const START: 'discovery__start' = 'discovery__start'; export const START: 'discovery__start' = 'discovery__start';
export const STOP: 'discovery__stop' = 'discovery__stop'; export const STOP: 'discovery__stop' = 'discovery__stop';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const SAVE: 'storage__save' = 'storage__save'; export const SAVE: 'storage__save' = 'storage__save';
export const READY: 'storage__ready' = 'storage__ready'; export const READY: 'storage__ready' = 'storage__ready';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const OPEN: 'log__open' = 'log__open'; export const OPEN: 'log__open' = 'log__open';
export const CLOSE: 'log__close' = 'log__close'; export const CLOSE: 'log__close' = 'log__close';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const ON_PASSPHRASE_CHANGE: 'action__on_passphrase_change' = 'action__on_passphrase_change'; 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'; export const ON_PASSPHRASE_SHOW: 'action__on_passphrase_show' = 'action__on_passphrase_show';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const ADD: 'notification__add' = 'notification__add'; export const ADD: 'notification__add' = 'notification__add';
export const CLOSE: 'notification__close' = 'notification__close'; export const CLOSE: 'notification__close' = 'notification__close';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage'; export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage';
export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved'; export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const INIT: 'receive__init' = 'receive__init'; export const INIT: 'receive__init' = 'receive__init';
export const DISPOSE: 'receive__dispose' = 'receive__dispose'; export const DISPOSE: 'receive__dispose' = 'receive__dispose';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const INIT: 'send__init' = 'send__init'; export const INIT: 'send__init' = 'send__init';
export const DISPOSE: 'send__dispose' = 'send__dispose'; export const DISPOSE: 'send__dispose' = 'send__dispose';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const INIT: 'summary__init' = 'summary__init'; export const INIT: 'summary__init' = 'summary__init';
export const DISPOSE: 'summary__dispose' = 'summary__dispose'; export const DISPOSE: 'summary__dispose' = 'summary__dispose';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const ADD: 'token__add' = 'token__add'; export const ADD: 'token__add' = 'token__add';
export const REMOVE: 'token__remove' = 'token__remove'; export const REMOVE: 'token__remove' = 'token__remove';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const ON_BEFORE_UNLOAD: 'wallet__on_before_unload' = 'wallet__on_before_unload'; export const ON_BEFORE_UNLOAD: 'wallet__on_before_unload' = 'wallet__on_before_unload';
export const TOGGLE_DEVICE_DROPDOWN: 'wallet__toggle_dropdown' = 'wallet__toggle_dropdown'; export const TOGGLE_DEVICE_DROPDOWN: 'wallet__toggle_dropdown' = 'wallet__toggle_dropdown';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
export const START: 'web3__start' = 'web3__start'; export const START: 'web3__start' = 'web3__start';
export const STOP: 'web3__stop' = 'web3__stop'; export const STOP: 'web3__stop' = 'web3__stop';

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -12,26 +12,20 @@ type Props = {
toggle: typeof LogActions.toggle toggle: typeof LogActions.toggle
} }
const Footer = (props: Props): React$Element<string> => { const Footer = (props: Props): React$Element<string> => (
return ( <footer>
<footer> <span>© 2018</span>
<span>© 2018</span> <a href="http://satoshilabs.com" target="_blank" rel="noreferrer noopener" className="satoshi green">SatoshiLabs</a>
<a href="http://satoshilabs.com" target="_blank" rel="noreferrer noopener" className="satoshi green">SatoshiLabs</a> <a href="tos.pdf" target="_blank" rel="noreferrer noopener" className="green">Terms</a>
<a href="tos.pdf" target="_blank" rel="noreferrer noopener" className="green">Terms</a> <a onClick={props.toggle} className="green">Show Log</a>
<a onClick={ props.toggle } className="green">Show Log</a> </footer>
</footer> );
);
} export default connect(
(state: State) => ({
export default connect( }),
(state: State) => { (dispatch: Dispatch) => ({
return { toggle: bindActionCreators(LogActions.toggle, dispatch),
}),
}
},
(dispatch: Dispatch) => {
return {
toggle: bindActionCreators(LogActions.toggle, dispatch),
};
}
)(Footer); )(Footer);

@ -1,30 +1,28 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
const Header = (): React$Element<string> => { const Header = (): React$Element<string> => (
return ( <header>
<header> <div className="layout-wrapper">
<div className="layout-wrapper"> <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 163.7 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 163.7 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet"> <polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1" />
<polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1"/> <path d="M158.8,26.9c2.1-0.8,4.3-2.9,4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1,7.5h6.7L158.8,26.9z M154.7,22.5 h-4V18h4c1.5,0,2.5,0.9,2.5,2.2C157.2,21.6,156.2,22.5,154.7,22.5z" />
<path d="M158.8,26.9c2.1-0.8,4.3-2.9,4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1,7.5h6.7L158.8,26.9z M154.7,22.5 h-4V18h4c1.5,0,2.5,0.9,2.5,2.2C157.2,21.6,156.2,22.5,154.7,22.5z"/> <path d="M130.8,12.5c-6.8,0-11.6,4.9-11.6,11.5s4.9,11.5,11.6,11.5s11.7-4.9,11.7-11.5S137.6,12.5,130.8,12.5z M130.8,30.3 c-3.4,0-5.7-2.6-5.7-6.3c0-3.8,2.3-6.3,5.7-6.3c3.4,0,5.8,2.6,5.8,6.3C136.6,27.7,134.2,30.3,130.8,30.3z" />
<path d="M130.8,12.5c-6.8,0-11.6,4.9-11.6,11.5s4.9,11.5,11.6,11.5s11.7-4.9,11.7-11.5S137.6,12.5,130.8,12.5z M130.8,30.3 c-3.4,0-5.7-2.6-5.7-6.3c0-3.8,2.3-6.3,5.7-6.3c3.4,0,5.8,2.6,5.8,6.3C136.6,27.7,134.2,30.3,130.8,30.3z"/> <polygon points="82.1,12.8 98.3,12.8 98.3,18 87.9,18 87.9,21.3 98,21.3 98,26.4 87.9,26.4 87.9,30 98.3,30 98.3,35.2 82.1,35.2 " />
<polygon points="82.1,12.8 98.3,12.8 98.3,18 87.9,18 87.9,21.3 98,21.3 98,26.4 87.9,26.4 87.9,30 98.3,30 98.3,35.2 82.1,35.2 "/> <path d="M24.6,9.7C24.6,4.4,20,0,14.4,0S4.2,4.4,4.2,9.7v3.1H0v22.3h0l14.4,6.7l14.4-6.7h0V12.9h-4.2V9.7z M9.4,9.7 c0-2.5,2.2-4.5,5-4.5s5,2,5,4.5v3.1H9.4V9.7z M23,31.5l-8.6,4l-8.6-4V18.1H23V31.5z" />
<path d="M24.6,9.7C24.6,4.4,20,0,14.4,0S4.2,4.4,4.2,9.7v3.1H0v22.3h0l14.4,6.7l14.4-6.7h0V12.9h-4.2V9.7z M9.4,9.7 c0-2.5,2.2-4.5,5-4.5s5,2,5,4.5v3.1H9.4V9.7z M23,31.5l-8.6,4l-8.6-4V18.1H23V31.5z"/> <path d="M79.4,20.3c0-4.5-3.1-7.4-7.7-7.4H61.2v22.3H67v-7.5h2.2l4.1,7.5H80l-4.9-8.3C77.2,26.1,79.4,24,79.4,20.3z M71,22.5h-4V18 h4c1.5,0,2.5,0.9,2.5,2.2C73.5,21.6,72.5,22.5,71,22.5z" />
<path d="M79.4,20.3c0-4.5-3.1-7.4-7.7-7.4H61.2v22.3H67v-7.5h2.2l4.1,7.5H80l-4.9-8.3C77.2,26.1,79.4,24,79.4,20.3z M71,22.5h-4V18 h4c1.5,0,2.5,0.9,2.5,2.2C73.5,21.6,72.5,22.5,71,22.5z"/> <polygon points="40.5,12.8 58.6,12.8 58.6,18.1 52.4,18.1 52.4,35.2 46.6,35.2 46.6,18.1 40.5,18.1 " />
<polygon points="40.5,12.8 58.6,12.8 58.6,18.1 52.4,18.1 52.4,35.2 46.6,35.2 46.6,18.1 40.5,18.1 "/> </svg>
</svg> <div>
<div> <a href="https://trezor.io/" target="_blank" rel="noreferrer noopener">TREZOR</a>
<a href="https://trezor.io/" target="_blank" rel="noreferrer noopener">TREZOR</a> <a href="https://doc.satoshilabs.com/trezor-user/" target="_blank" rel="noreferrer noopener">Docs</a>
<a href="https://doc.satoshilabs.com/trezor-user/" target="_blank" rel="noreferrer noopener">Docs</a> <a href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</a>
<a href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</a> <a href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</a>
<a href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</a>
</div>
</div> </div>
</header> </div>
); </header>
} );
export default Header; export default Header;

@ -1,17 +1,16 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
export default (props: { size: string, label?: string }): React$Element<string> => { export default (props: { size: string, label?: string }): React$Element<string> => {
const style = { const style = {
width: `${props.size}px`, width: `${props.size}px`,
height: `${props.size}px`, height: `${props.size}px`,
} };
return ( return (
<div className="loader-circle" style={ style }> <div className="loader-circle" style={style}>
<p>{ props.label }</p> <p>{ props.label }</p>
<svg className="circular" viewBox="25 25 50 50"> <svg className="circular" viewBox="25 25 50 50">
<circle className="route" cx="50" cy="50" r="20" fill="none" stroke="" strokeWidth="1" strokeMiterlimit="10" /> <circle className="route" cx="50" cy="50" r="20" fill="none" stroke="" strokeWidth="1" strokeMiterlimit="10" />
@ -19,4 +18,4 @@ export default (props: { size: string, label?: string }): React$Element<string>
</svg> </svg>
</div> </div>
); );
} };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -14,34 +14,29 @@ type Props = {
} }
const Log = (props: Props): ?React$Element<string> => { const Log = (props: Props): ?React$Element<string> => {
if (!props.log.opened) if (!props.log.opened) return null;
return null;
// const entries = props.log.entries.map(entry => { // const entries = props.log.entries.map(entry => {
// return ( // return (
// ) // )
// }) // })
return ( return (
<div className="log"> <div className="log">
<button className="log-close transparent" onClick={ props.toggle }></button> <button className="log-close transparent" onClick={props.toggle} />
<h2>Log</h2> <h2>Log</h2>
<p>Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.</p> <p>Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.</p>
<textarea value={ JSON.stringify(props.log.entries) } readOnly></textarea> <textarea value={JSON.stringify(props.log.entries)} readOnly />
</div> </div>
) );
} };
export default connect( export default connect(
(state: State) => { (state: State) => ({
return { log: state.log,
log: state.log }),
}; (dispatch: Dispatch) => ({
}, toggle: bindActionCreators(LogActions.toggle, dispatch),
(dispatch: Dispatch) => { }),
return {
toggle: bindActionCreators(LogActions.toggle, dispatch),
};
}
)(Log); )(Log);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -24,24 +24,24 @@ type NProps = {
close?: typeof NotificationActions.close close?: typeof NotificationActions.close
} }
export const Notification = (props: NProps): React$Element<string> => { export const Notification = (props: NProps): React$Element<string> => {
const className = `notification ${ props.className }`; const className = `notification ${props.className}`;
const close: Function = typeof props.close === 'function' ? props.close : () => {}; // TODO: add default close action const close: Function = typeof props.close === 'function' ? props.close : () => {}; // TODO: add default close action
const actionButtons = props.actions ? props.actions.map((a, i) => { const actionButtons = props.actions ? props.actions.map((a, i) => (
return ( <button key={i} onClick={(event) => { close(); a.callback(); }} className="transparent">{ a.label }</button>
<button key={ i } onClick={ event => { close(); a.callback(); } } className="transparent">{ a.label }</button> )) : null;
)
}) : null;
return ( return (
<div className={ className }> <div className={className}>
{ props.cancelable ? ( { props.cancelable ? (
<button className="notification-close transparent" <button
onClick={ event => close() }></button> className="notification-close transparent"
onClick={event => close()}
/>
) : null } ) : null }
<div className="notification-body"> <div className="notification-body">
<h2>{ props.title }</h2> <h2>{ props.title }</h2>
{ props.message ? (<p dangerouslySetInnerHTML={{__html: props.message }}></p>) : null } { props.message ? (<p dangerouslySetInnerHTML={{ __html: props.message }} />) : null }
</div> </div>
{ props.actions && props.actions.length > 0 ? ( { props.actions && props.actions.length > 0 ? (
<div className="notification-action"> <div className="notification-action">
@ -50,35 +50,29 @@ export const Notification = (props: NProps): React$Element<string> => {
) : null } ) : null }
</div> </div>
) );
} };
export const NotificationGroup = (props: Props) => { export const NotificationGroup = (props: Props) => {
const { notifications, close } = props; const { notifications, close } = props;
return notifications.map((n, i) => { return notifications.map((n, i) => (
return ( <Notification
<Notification key={i}
key={i} className={n.type}
className={ n.type } title={n.title}
title={ n.title } message={n.message}
message={ n.message } cancelable={n.cancelable}
cancelable={ n.cancelable } actions={n.actions}
actions={ n.actions } close={close}
close={ close } />
/> ));
) };
});
}
export default connect( export default connect(
(state: State) => { (state: State) => ({
return { notifications: state.notifications,
notifications: state.notifications }),
}; (dispatch: Dispatch) => ({
}, close: bindActionCreators(NotificationActions.close, dispatch),
(dispatch: Dispatch) => { }),
return {
close: bindActionCreators(NotificationActions.close, dispatch),
};
}
)(NotificationGroup); )(NotificationGroup);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import TrezorConnect from 'trezor-connect'; import TrezorConnect from 'trezor-connect';
@ -25,41 +25,38 @@ const DisconnectDevice = (props: Props) => {
</span> </span>
</p> </p>
</div> </div>
<div className="image"> <div className="image" />
</div>
</main> </main>
) );
} };
const ConnectHIDDevice = (props: Props) => { const ConnectHIDDevice = (props: Props) => (
return ( <main>
<main> <h2 className="claim">The private bank in your hands.</h2>
<h2 className="claim">The private bank in your hands.</h2> <p>TREZOR Wallet is an easy-to-use interface for your TREZOR.</p>
<p>TREZOR Wallet is an easy-to-use interface for your TREZOR.</p> <p>TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.</p>
<p>TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.</p> <div className="row">
<div className="row"> <p className="connect">
<p className="connect"> <span>
<span> <svg width="12px" height="35px" viewBox="0 0 20 57">
<svg width="12px" height="35px" viewBox="0 0 20 57"> <g stroke="none" strokeWidth="1" fill="none" transform="translate(1, 1)">
<g stroke="none" strokeWidth="1" fill="none" transform="translate(1, 1)"> <rect className="connect-usb-pin" fill="#01B757" x="6" y="39" width="6" height="5" />
<rect className="connect-usb-pin" fill="#01B757" x="6" y="39" width="6" height="5"></rect> <rect className="connect-usb-cable" stroke="#01B757" strokeWidth="1" x="8.5" y="44.5" width="1" height="11" />
<rect className="connect-usb-cable" stroke="#01B757" strokeWidth="1" x="8.5" y="44.5" width="1" height="11"></rect> <path stroke="#01B757" d="M8.90856859,33.9811778 L6.43814432,33.9811778 C5.45301486,34.0503113 4.69477081,33.6889084 4.1634122,32.8969691 C3.36637428,31.7090602 -0.000402169348,26.3761977 0.0748097911,23.2982514 C0.124878873,21.2492429 0.0999525141,14.5598149 3.07156595e-05,3.22996744 C-0.000274213164,3.1963928 0.00243636275,3.162859 0.00812115776,3.12976773 C0.28477346,1.51937083 1.22672004,0.617538852 2.8339609,0.424271782 C4.45813658,0.228968338 6.54411954,0.0875444105 9.09190977,0 L9.09190977,0.0169167084 C11.5566027,0.104886477 13.5814718,0.244169993 15.1665175,0.434768145 C16.7530267,0.625542287 17.6912941,1.50671985 17.9813196,3.07830083 C17.9943481,3.14889902 18.0005888,3.22058224 17.9999563,3.29236974 L17.9999901,3.29237004 C17.9004498,14.5907444 17.875676,21.2628703 17.9256686,23.3087478 C18.0008805,26.3866941 14.6341041,31.7195566 13.8370662,32.9074655 C13.3057075,33.6994047 12.5474635,34.0608076 11.562334,33.9916742 L8.90856859,33.9916742 L8.90856859,33.9811778 Z" />
<path stroke="#01B757" d="M8.90856859,33.9811778 L6.43814432,33.9811778 C5.45301486,34.0503113 4.69477081,33.6889084 4.1634122,32.8969691 C3.36637428,31.7090602 -0.000402169348,26.3761977 0.0748097911,23.2982514 C0.124878873,21.2492429 0.0999525141,14.5598149 3.07156595e-05,3.22996744 C-0.000274213164,3.1963928 0.00243636275,3.162859 0.00812115776,3.12976773 C0.28477346,1.51937083 1.22672004,0.617538852 2.8339609,0.424271782 C4.45813658,0.228968338 6.54411954,0.0875444105 9.09190977,0 L9.09190977,0.0169167084 C11.5566027,0.104886477 13.5814718,0.244169993 15.1665175,0.434768145 C16.7530267,0.625542287 17.6912941,1.50671985 17.9813196,3.07830083 C17.9943481,3.14889902 18.0005888,3.22058224 17.9999563,3.29236974 L17.9999901,3.29237004 C17.9004498,14.5907444 17.875676,21.2628703 17.9256686,23.3087478 C18.0008805,26.3866941 14.6341041,31.7195566 13.8370662,32.9074655 C13.3057075,33.6994047 12.5474635,34.0608076 11.562334,33.9916742 L8.90856859,33.9916742 L8.90856859,33.9811778 Z"></path> <rect fill="#01B757" x="2" y="7" width="14" height="7" rx="0.5625" />
<rect fill="#01B757" x="2" y="7" width="14" height="7" rx="0.5625"></rect> </g>
</g> </svg>
</svg>
Connect TREZOR to continue Connect TREZOR to continue
</span> </span>
</p> </p>
</div> </div>
<div className="image"> <div className="image">
<p> <p>
<span>Don't have TREZOR? <a href="https://trezor.io/" className="green" target="_blank" rel="noreferrer noopener">Get one</a></span> <span>Don't have TREZOR? <a href="https://trezor.io/" className="green" target="_blank" rel="noreferrer noopener">Get one</a></span>
</p> </p>
</div> </div>
</main> </main>
); );
}
class ConnectWebUsbDevice extends Component<Props> { class ConnectWebUsbDevice extends Component<Props> {
componentDidMount(): void { componentDidMount(): void {
@ -81,10 +78,10 @@ class ConnectWebUsbDevice extends Component<Props> {
<span> <span>
<svg width="12px" height="35px" viewBox="0 0 20 57"> <svg width="12px" height="35px" viewBox="0 0 20 57">
<g stroke="none" strokeWidth="1" fill="none" transform="translate(1, 1)"> <g stroke="none" strokeWidth="1" fill="none" transform="translate(1, 1)">
<rect className="connect-usb-pin" fill="#01B757" x="6" y="39" width="6" height="5"></rect> <rect className="connect-usb-pin" fill="#01B757" x="6" y="39" width="6" height="5" />
<rect className="connect-usb-cable" stroke="#01B757" strokeWidth="1" x="8.5" y="44.5" width="1" height="11"></rect> <rect className="connect-usb-cable" stroke="#01B757" strokeWidth="1" x="8.5" y="44.5" width="1" height="11" />
<path stroke="#01B757" d="M8.90856859,33.9811778 L6.43814432,33.9811778 C5.45301486,34.0503113 4.69477081,33.6889084 4.1634122,32.8969691 C3.36637428,31.7090602 -0.000402169348,26.3761977 0.0748097911,23.2982514 C0.124878873,21.2492429 0.0999525141,14.5598149 3.07156595e-05,3.22996744 C-0.000274213164,3.1963928 0.00243636275,3.162859 0.00812115776,3.12976773 C0.28477346,1.51937083 1.22672004,0.617538852 2.8339609,0.424271782 C4.45813658,0.228968338 6.54411954,0.0875444105 9.09190977,0 L9.09190977,0.0169167084 C11.5566027,0.104886477 13.5814718,0.244169993 15.1665175,0.434768145 C16.7530267,0.625542287 17.6912941,1.50671985 17.9813196,3.07830083 C17.9943481,3.14889902 18.0005888,3.22058224 17.9999563,3.29236974 L17.9999901,3.29237004 C17.9004498,14.5907444 17.875676,21.2628703 17.9256686,23.3087478 C18.0008805,26.3866941 14.6341041,31.7195566 13.8370662,32.9074655 C13.3057075,33.6994047 12.5474635,34.0608076 11.562334,33.9916742 L8.90856859,33.9916742 L8.90856859,33.9811778 Z"></path> <path stroke="#01B757" d="M8.90856859,33.9811778 L6.43814432,33.9811778 C5.45301486,34.0503113 4.69477081,33.6889084 4.1634122,32.8969691 C3.36637428,31.7090602 -0.000402169348,26.3761977 0.0748097911,23.2982514 C0.124878873,21.2492429 0.0999525141,14.5598149 3.07156595e-05,3.22996744 C-0.000274213164,3.1963928 0.00243636275,3.162859 0.00812115776,3.12976773 C0.28477346,1.51937083 1.22672004,0.617538852 2.8339609,0.424271782 C4.45813658,0.228968338 6.54411954,0.0875444105 9.09190977,0 L9.09190977,0.0169167084 C11.5566027,0.104886477 13.5814718,0.244169993 15.1665175,0.434768145 C16.7530267,0.625542287 17.6912941,1.50671985 17.9813196,3.07830083 C17.9943481,3.14889902 18.0005888,3.22058224 17.9999563,3.29236974 L17.9999901,3.29237004 C17.9004498,14.5907444 17.875676,21.2628703 17.9256686,23.3087478 C18.0008805,26.3866941 14.6341041,31.7195566 13.8370662,32.9074655 C13.3057075,33.6994047 12.5474635,34.0608076 11.562334,33.9916742 L8.90856859,33.9916742 L8.90856859,33.9811778 Z" />
<rect fill="#01B757" x="2" y="7" width="14" height="7" rx="0.5625"></rect> <rect fill="#01B757" x="2" y="7" width="14" height="7" rx="0.5625" />
</g> </g>
</svg> </svg>
Connect TREZOR Connect TREZOR
@ -107,12 +104,11 @@ class ConnectWebUsbDevice extends Component<Props> {
const ConnectDevice = (props: Props) => { const ConnectDevice = (props: Props) => {
const { transport, disconnectRequest } = props; const { transport, disconnectRequest } = props;
if (disconnectRequest) { if (disconnectRequest) {
return <DisconnectDevice {...props} /> return <DisconnectDevice {...props} />;
} else if (transport && transport.version.indexOf('webusb') >= 0) { } if (transport && transport.version.indexOf('webusb') >= 0) {
return <ConnectWebUsbDevice {...props} /> return <ConnectWebUsbDevice {...props} />;
} else {
return <ConnectHIDDevice {...props} />
} }
} return <ConnectHIDDevice {...props} />;
};
export default ConnectDevice; export default ConnectDevice;

@ -1,9 +1,9 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Preloader from './Preloader';
import Select from 'react-select'; import Select from 'react-select';
import Preloader from './Preloader';
type State = { type State = {
version: string; version: string;
@ -35,7 +35,6 @@ type Props = {
} }
export default class InstallBridge extends Component<Props, State> { export default class InstallBridge extends Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -49,7 +48,7 @@ export default class InstallBridge extends Component<Props, State> {
onChange(value: InstallTarget) { onChange(value: InstallTarget) {
this.setState({ this.setState({
target: value target: value,
}); });
} }
@ -57,8 +56,8 @@ export default class InstallBridge extends Component<Props, State> {
if (this.props.browserState.osname && !this.state.target) { if (this.props.browserState.osname && !this.state.target) {
const currentTarget: ?InstallTarget = installers.find(i => i.id === this.props.browserState.osname); const currentTarget: ?InstallTarget = installers.find(i => i.id === this.props.browserState.osname);
this.setState({ this.setState({
target: currentTarget target: currentTarget,
}) });
} }
} }
@ -67,8 +66,8 @@ export default class InstallBridge extends Component<Props, State> {
return <Preloader />; return <Preloader />;
} }
const label: string = this.state.target.label; const label: string = this.state.target.label;
const url = `${ this.state.url }${ this.state.target.value }`; const url = `${this.state.url}${this.state.target.value}`;
return ( return (
<main> <main>
<h3 className="claim">TREZOR Bridge. <span>Version 2.0.12</span></h3> <h3 className="claim">TREZOR Bridge. <span>Version 2.0.12</span></h3>
@ -77,12 +76,13 @@ export default class InstallBridge extends Component<Props, State> {
<Select <Select
name="installers" name="installers"
className="installers" className="installers"
searchable={ false } searchable={false}
clearable= { false } clearable={false}
value={ this.state.target } value={this.state.target}
onChange={ this.onChange.bind(this) } onChange={this.onChange.bind(this)}
options={ installers } /> options={installers}
<a href={ url } className="button">Download for { label }</a> />
<a href={url} className="button">Download for { label }</a>
</div> </div>
<p>Learn more about latest version in <a href="https://github.com/trezor/trezord-go/blob/master/CHANGELOG.md" className="green" target="_blank" rel="noreferrer noopener">Changelog</a></p> <p>Learn more about latest version in <a href="https://github.com/trezor/trezord-go/blob/master/CHANGELOG.md" className="green" target="_blank" rel="noreferrer noopener">Changelog</a></p>
</main> </main>

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import Preloader from './Preloader'; import Preloader from './Preloader';
@ -14,29 +14,25 @@ import Notifications, { Notification } from '../common/Notification';
import type { Props } from './index'; import type { Props } from './index';
const BrowserNotSupported = (props: {}): React$Element<string> => { const BrowserNotSupported = (props: {}): React$Element<string> => (
return ( <main>
<main> <h2>Your browser is not supported</h2>
<h2>Your browser is not supported</h2> <p>Please choose one of the supported browsers</p>
<p>Please choose one of the supported browsers</p> <div className="row">
<div className="row"> <div className="chrome">
<div className="chrome"> <p>Google Chrome</p>
<p>Google Chrome</p> <a className="button" href="https://www.google.com/chrome/" target="_blank" rel="noreferrer noopener">Get Chrome</a>
<a className="button" href="https://www.google.com/chrome/" target="_blank" rel="noreferrer noopener">Get Chrome</a>
</div>
<div className="firefox">
<p>Mozilla Firefox</p>
<a className="button" href="https://www.mozilla.org/en-US/firefox/new/" target="_blank" rel="noreferrer noopener">Get Firefox</a>
</div>
</div> </div>
</main> <div className="firefox">
) <p>Mozilla Firefox</p>
} <a className="button" href="https://www.mozilla.org/en-US/firefox/new/" target="_blank" rel="noreferrer noopener">Get Firefox</a>
</div>
</div>
</main>
);
export default (props: Props) => { export default (props: Props) => {
const web3 = props.web3; const web3 = props.web3;
const { devices } = props; const { devices } = props;
const { browserState, transport } = props.connect; const { browserState, transport } = props.connect;
@ -49,26 +45,28 @@ export default (props: Props) => {
const bridgeRoute: boolean = props.router.location.state.hasOwnProperty('bridge'); const bridgeRoute: boolean = props.router.location.state.hasOwnProperty('bridge');
if (localStorageError) { if (localStorageError) {
notification = (<Notification notification = (
title="Initialization error" <Notification
message="Config files are missing" title="Initialization error"
className="error" message="Config files are missing"
/>); className="error"
/>
);
css += ' config-error'; css += ' config-error';
} else if (browserState.supported === false) { } else if (browserState.supported === false) {
css += ' browser-not-supported' css += ' browser-not-supported';
body = <BrowserNotSupported />; body = <BrowserNotSupported />;
} else if (connectError || bridgeRoute) { } else if (connectError || bridgeRoute) {
css += ' install-bridge'; css += ' install-bridge';
body = <InstallBridge browserState={ browserState } />; body = <InstallBridge browserState={browserState} />;
} else if (props.wallet.ready && devices.length < 1) { } else if (props.wallet.ready && devices.length < 1) {
css += ' connect-device'; css += ' connect-device';
body = <ConnectDevice transport={ transport } disconnectRequest={ props.wallet.disconnectRequest } />; body = <ConnectDevice transport={transport} disconnectRequest={props.wallet.disconnectRequest} />;
} }
if (notification || body) { if (notification || body) {
return ( return (
<div className={ css }> <div className={css}>
<Header /> <Header />
{ notification } { notification }
<Notifications /> <Notifications />
@ -77,7 +75,6 @@ export default (props: Props) => {
<Footer /> <Footer />
</div> </div>
); );
} else {
return (<Preloader />);
} }
} return (<Preloader />);
};

@ -1,12 +1,10 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
export default (props: {}): React$Element<string> => { export default (props: {}): React$Element<string> => (
return ( <section className="landing">
<section className="landing">
localstorage ERROR localstorage ERROR
</section> </section>
); );
}

@ -1,13 +1,11 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import Loader from '../common/LoaderCircle'; import Loader from '../common/LoaderCircle';
export default (props: {}): React$Element<string> => { export default (props: {}): React$Element<string> => (
return ( <section className="landing">
<section className="landing"> <Loader label="Loading" size="100" />
<Loader label="Loading" size="100" /> </section>
</section> );
);
}

@ -1,12 +1,10 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
export default (props: {}): React$Element<string> => { export default (props: {}): React$Element<string> => (
return ( <section className="landing">
<section className="landing">
connect ERROR connect ERROR
</section> </section>
); );
}

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -26,28 +26,24 @@ type DispatchProps = {
} }
type OwnProps = { type OwnProps = {
} }
export type Props = StateProps & DispatchProps; export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
return { localStorage: state.localStorage,
localStorage: state.localStorage, modal: state.modal,
modal: state.modal, web3: state.web3,
web3: state.web3, wallet: state.wallet,
wallet: state.wallet, connect: state.connect,
connect: state.connect, router: state.router,
router: state.router, wallet: state.wallet,
wallet: state.wallet, devices: state.devices,
devices: state.devices, });
};
}
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
return {
});
};
}
export default connect(mapStateToProps, mapDispatchToProps)(LandingPage); export default connect(mapStateToProps, mapDispatchToProps)(LandingPage);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { findAccount } from '~/js/reducers/AccountsReducer'; import { findAccount } from '~/js/reducers/AccountsReducer';
@ -7,10 +7,9 @@ import { findAccount } from '~/js/reducers/AccountsReducer';
import type { Props } from './index'; import type { Props } from './index';
const ConfirmAddress = (props: Props) => { const ConfirmAddress = (props: Props) => {
const { const {
account, account,
network network,
} = props.selectedAccount; } = props.selectedAccount;
if (!account || !network) return null; if (!account || !network) return null;
@ -26,11 +25,10 @@ const ConfirmAddress = (props: Props) => {
</div> </div>
</div> </div>
); );
} };
export default ConfirmAddress; export default ConfirmAddress;
export class ConfirmUnverifiedAddress extends Component<Props> { export class ConfirmUnverifiedAddress extends Component<Props> {
keyboardHandler: (event: KeyboardEvent) => void; keyboardHandler: (event: KeyboardEvent) => void;
keyboardHandler(event: KeyboardEvent): void { keyboardHandler(event: KeyboardEvent): void {
@ -52,18 +50,17 @@ export class ConfirmUnverifiedAddress extends Component<Props> {
verifyAddress() { verifyAddress() {
if (!this.props.modal.opened) return; if (!this.props.modal.opened) return;
const { const {
account account,
} = this.props.selectedAccount; } = this.props.selectedAccount;
if (!account) return null; if (!account) return null;
this.props.modalActions.onCancel(); this.props.modalActions.onCancel();
this.props.receiveActions.showAddress(account.addressPath); this.props.receiveActions.showAddress(account.addressPath);
} }
showUnverifiedAddress() { showUnverifiedAddress() {
if (!this.props.modal.opened) return; if (!this.props.modal.opened) return;
this.props.modalActions.onCancel(); this.props.modalActions.onCancel();
this.props.receiveActions.showUnverifiedAddress(); this.props.receiveActions.showUnverifiedAddress();
} }
@ -71,33 +68,33 @@ export class ConfirmUnverifiedAddress extends Component<Props> {
render() { render() {
if (!this.props.modal.opened) return null; if (!this.props.modal.opened) return null;
const { const {
device device,
} = this.props.modal; } = this.props.modal;
const { const {
onCancel onCancel,
} = this.props.modalActions; } = this.props.modalActions;
let deviceStatus: string; let deviceStatus: string;
let claim: string; let claim: string;
if (!device.connected) { if (!device.connected) {
deviceStatus = `${ device.label } is not connected`; deviceStatus = `${device.label} is not connected`;
claim = 'Please connect your device' claim = 'Please connect your device';
} else { } else {
// corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings // corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings
const enable: string = device.features && device.features.passphrase_protection ? 'enable' : 'disable'; const enable: string = device.features && device.features.passphrase_protection ? 'enable' : 'disable';
deviceStatus = `${ device.label } is unavailable`; deviceStatus = `${device.label} is unavailable`;
claim = `Please ${ enable } passphrase settings`; claim = `Please ${enable} passphrase settings`;
} }
return ( return (
<div className="confirm-address-unverified"> <div className="confirm-address-unverified">
<button className="close-modal transparent" onClick={ onCancel }></button> <button className="close-modal transparent" onClick={onCancel} />
<h3>{ deviceStatus }</h3> <h3>{ deviceStatus }</h3>
<p>To prevent phishing attacks, you should verify the address on your TREZOR first. { claim } to continue with the verification process.</p> <p>To prevent phishing attacks, you should verify the address on your TREZOR first. { claim } to continue with the verification process.</p>
<button onClick={ event => this.verifyAddress() }>Try again</button> <button onClick={event => this.verifyAddress()}>Try again</button>
<button className="white" onClick={ event => this.showUnverifiedAddress() }>Show unverified address</button> <button className="white" onClick={event => this.showUnverifiedAddress()}>Show unverified address</button>
</div> </div>
); );
} }

@ -1,11 +1,10 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import type { Props } from './index'; import type { Props } from './index';
const Confirmation = (props: Props) => { const Confirmation = (props: Props) => {
if (!props.modal.opened) return null; if (!props.modal.opened) return null;
const { device } = props.modal; const { device } = props.modal;
@ -14,7 +13,7 @@ const Confirmation = (props: Props) => {
address, address,
currency, currency,
total, total,
selectedFeeLevel selectedFeeLevel,
} = props.sendForm; } = props.sendForm;
return ( return (
@ -25,7 +24,7 @@ const Confirmation = (props: Props) => {
</div> </div>
<div className="content"> <div className="content">
<label>Send </label> <label>Send </label>
<p>{ `${amount} ${ currency }` }</p> <p>{ `${amount} ${currency}` }</p>
<label>To</label> <label>To</label>
<p>{ address }</p> <p>{ address }</p>
<label>Fee</label> <label>Fee</label>
@ -33,6 +32,6 @@ const Confirmation = (props: Props) => {
</div> </div>
</div> </div>
); );
} };
export default Confirmation; export default Confirmation;

@ -1,8 +1,8 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { getNewInstance } from '~/js/reducers/DevicesReducer' import { getNewInstance } from '~/js/reducers/DevicesReducer';
import type { Props } from './index'; import type { Props } from './index';
type State = { type State = {
@ -13,9 +13,10 @@ type State = {
} }
export default class DuplicateDevice extends Component<Props, State> { export default class DuplicateDevice extends Component<Props, State> {
keyboardHandler: (event: KeyboardEvent) => void; keyboardHandler: (event: KeyboardEvent) => void;
state: State; state: State;
input: ?HTMLInputElement; input: ?HTMLInputElement;
constructor(props: Props) { constructor(props: Props) {
@ -31,7 +32,7 @@ export default class DuplicateDevice extends Component<Props, State> {
instance, instance,
instanceName: null, instanceName: null,
isUsed: false, isUsed: false,
} };
} }
keyboardHandler(event: KeyboardEvent): void { keyboardHandler(event: KeyboardEvent): void {
@ -43,8 +44,7 @@ export default class DuplicateDevice extends Component<Props, State> {
componentDidMount(): void { componentDidMount(): void {
// one time autofocus // one time autofocus
if (this.input) if (this.input) this.input.focus();
this.input.focus();
this.keyboardHandler = this.keyboardHandler.bind(this); this.keyboardHandler = this.keyboardHandler.bind(this);
window.addEventListener('keydown', this.keyboardHandler, false); window.addEventListener('keydown', this.keyboardHandler, false);
} }
@ -54,25 +54,23 @@ export default class DuplicateDevice extends Component<Props, State> {
} }
onNameChange = (value: string): void => { onNameChange = (value: string): void => {
let isUsed: boolean = false; let isUsed: boolean = false;
if (value.length > 0) { if (value.length > 0) {
isUsed = ( this.props.devices.find(d => d.instanceName === value) !== undefined ); isUsed = (this.props.devices.find(d => d.instanceName === value) !== undefined);
} }
this.setState({ this.setState({
instanceName: value.length > 0 ? value : null, instanceName: value.length > 0 ? value : null,
isUsed isUsed,
}); });
} }
submit() { submit() {
if (!this.props.modal.opened) return; if (!this.props.modal.opened) return;
this.props.modalActions.onDuplicateDevice( { ...this.props.modal.device, instanceName: this.state.instanceName, instance: this.state.instance } ); this.props.modalActions.onDuplicateDevice({ ...this.props.modal.device, instanceName: this.state.instanceName, instance: this.state.instance });
} }
render() { render() {
if (!this.props.modal.opened) return null; if (!this.props.modal.opened) return null;
const { device } = this.props.modal; const { device } = this.props.modal;
@ -80,31 +78,32 @@ export default class DuplicateDevice extends Component<Props, State> {
const { const {
defaultName, defaultName,
instanceName, instanceName,
isUsed isUsed,
} = this.state; } = this.state;
return ( return (
<div className="duplicate"> <div className="duplicate">
<button className="close-modal transparent" onClick={ onCancel }></button> <button className="close-modal transparent" onClick={onCancel} />
<h3>Clone { device.label }?</h3> <h3>Clone { device.label }?</h3>
<p>This will create new instance of device which can be used with different passphrase</p> <p>This will create new instance of device which can be used with different passphrase</p>
<div className="row"> <div className="row">
<label>Instance name</label> <label>Instance name</label>
<input <input
type="text" type="text"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
className={ isUsed ? 'not-valid' : null } className={isUsed ? 'not-valid' : null}
placeholder={ defaultName } placeholder={defaultName}
ref={ (element) => { this.input = element; } } ref={(element) => { this.input = element; }}
onChange={ event => this.onNameChange(event.currentTarget.value) } onChange={event => this.onNameChange(event.currentTarget.value)}
defaultValue={ instanceName } /> defaultValue={instanceName}
/>
{ isUsed ? <span className="error">Instance name is already in use</span> : null } { isUsed ? <span className="error">Instance name is already in use</span> : null }
</div> </div>
<button disabled={ isUsed } onClick={ event => this.submit() }>Create new instance</button> <button disabled={isUsed} onClick={event => this.submit()}>Create new instance</button>
<button className="white" onClick={ onCancel }>Cancel</button> <button className="white" onClick={onCancel}>Cancel</button>
</div> </div>
); );
} }

@ -1,12 +1,12 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import type { Props } from './index'; import type { Props } from './index';
const InvalidPin = (props: Props) => { const InvalidPin = (props: Props) => {
if (!props.modal.opened) return null; if (!props.modal.opened) return null;
const { device } = props.modal; const { device } = props.modal;
return ( return (
<div className="pin"> <div className="pin">
@ -14,6 +14,6 @@ const InvalidPin = (props: Props) => {
<p>Retrying...</p> <p>Retrying...</p>
</div> </div>
); );
} };
export default InvalidPin; export default InvalidPin;

@ -1,10 +1,11 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import raf from 'raf'; import raf from 'raf';
import type { Props } from './index'; import type { Props } from './index';
type State = { type State = {
deviceLabel: string; deviceLabel: string;
singleInput: boolean; singleInput: boolean;
@ -18,10 +19,12 @@ type State = {
} }
export default class PinModal extends Component<Props, State> { export default class PinModal extends Component<Props, State> {
keyboardHandler: (event: KeyboardEvent) => void; keyboardHandler: (event: KeyboardEvent) => void;
state: State; state: State;
passphraseInput: ?HTMLInputElement; passphraseInput: ?HTMLInputElement;
passphraseRevisionInput: ?HTMLInputElement; passphraseRevisionInput: ?HTMLInputElement;
constructor(props: Props) { constructor(props: Props) {
@ -48,13 +51,11 @@ export default class PinModal extends Component<Props, State> {
passphraseRevisionFocused: false, passphraseRevisionFocused: false,
passphraseRevisionTouched: false, passphraseRevisionTouched: false,
match: true, match: true,
visible: false visible: false,
} };
} }
keyboardHandler(event: KeyboardEvent): void { keyboardHandler(event: KeyboardEvent): void {
if (event.keyCode === 13) { if (event.keyCode === 13) {
event.preventDefault(); event.preventDefault();
//this.passphraseInput.blur(); //this.passphraseInput.blur();
@ -65,7 +66,7 @@ export default class PinModal extends Component<Props, State> {
this.submit(); this.submit();
// TODO: set timeout, or wait for blur event // TODO: set timeout, or wait for blur event
//onPassphraseSubmit(passphrase, passphraseCached); //onPassphraseSubmit(passphrase, passphraseCached);
//raf(() => onPassphraseSubmit(passphrase)); //raf(() => onPassphraseSubmit(passphrase));
} }
@ -73,13 +74,11 @@ export default class PinModal extends Component<Props, State> {
componentDidMount(): void { componentDidMount(): void {
// one time autofocus // one time autofocus
if (this.passphraseInput) if (this.passphraseInput) this.passphraseInput.focus();
this.passphraseInput.focus();
this.keyboardHandler = this.keyboardHandler.bind(this); this.keyboardHandler = this.keyboardHandler.bind(this);
window.addEventListener('keydown', this.keyboardHandler, false); window.addEventListener('keydown', this.keyboardHandler, false);
// document.oncontextmenu = (event) => { // document.oncontextmenu = (event) => {
// const el = window.event.srcElement || event.target; // const el = window.event.srcElement || event.target;
// const type = el.tagName.toLowerCase() || ''; // const type = el.tagName.toLowerCase() || '';
@ -101,12 +100,12 @@ export default class PinModal extends Component<Props, State> {
// we don't want to keep password inside "value" attribute, // we don't want to keep password inside "value" attribute,
// so we need to replace it thru javascript // so we need to replace it thru javascript
componentDidUpdate() { componentDidUpdate() {
const { const {
passphrase, passphrase,
passphraseRevision, passphraseRevision,
passphraseFocused, passphraseFocused,
passphraseRevisionFocused, passphraseRevisionFocused,
visible visible,
} = this.state; } = this.state;
// } = this.props.modal; // } = this.props.modal;
@ -121,11 +120,11 @@ export default class PinModal extends Component<Props, State> {
if (this.passphraseInput) { if (this.passphraseInput) {
this.passphraseInput.value = passphraseInputValue; this.passphraseInput.value = passphraseInputValue;
this.passphraseInput.setAttribute("type", visible || (!visible && !passphraseFocused) ? "text" : "password"); this.passphraseInput.setAttribute('type', visible || (!visible && !passphraseFocused) ? 'text' : 'password');
} }
if (this.passphraseRevisionInput) { if (this.passphraseRevisionInput) {
this.passphraseRevisionInput.value = passphraseRevisionInputValue; this.passphraseRevisionInput.value = passphraseRevisionInputValue;
this.passphraseRevisionInput.setAttribute("type", visible || (!visible && !passphraseRevisionFocused) ? "text" : "password"); this.passphraseRevisionInput.setAttribute('type', visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password');
} }
} }
@ -136,13 +135,13 @@ export default class PinModal extends Component<Props, State> {
if (input === 'passphrase') { if (input === 'passphrase') {
this.setState({ this.setState({
match: this.state.singleInput || this.state.passphraseRevision === value, match: this.state.singleInput || this.state.passphraseRevision === value,
passphrase: value passphrase: value,
}); });
} else { } else {
this.setState({ this.setState({
match: this.state.passphrase === value, match: this.state.passphrase === value,
passphraseRevision: value, passphraseRevision: value,
passphraseRevisionTouched: true passphraseRevisionTouched: true,
}); });
} }
} }
@ -150,11 +149,11 @@ export default class PinModal extends Component<Props, State> {
onPassphraseFocus = (input: string): void => { onPassphraseFocus = (input: string): void => {
if (input === 'passphrase') { if (input === 'passphrase') {
this.setState({ this.setState({
passphraseFocused: true passphraseFocused: true,
}); });
} else { } else {
this.setState({ this.setState({
passphraseRevisionFocused: true passphraseRevisionFocused: true,
}); });
} }
} }
@ -162,24 +161,24 @@ export default class PinModal extends Component<Props, State> {
onPassphraseBlur = (input: string): void => { onPassphraseBlur = (input: string): void => {
if (input === 'passphrase') { if (input === 'passphrase') {
this.setState({ this.setState({
passphraseFocused: false passphraseFocused: false,
}); });
} else { } else {
this.setState({ this.setState({
passphraseRevisionFocused: false passphraseRevisionFocused: false,
}); });
} }
} }
onPassphraseShow = (): void => { onPassphraseShow = (): void => {
this.setState({ this.setState({
visible: true visible: true,
}); });
} }
onPassphraseHide = (): void => { onPassphraseHide = (): void => {
this.setState({ this.setState({
visible: false visible: false,
}); });
} }
@ -203,21 +202,20 @@ export default class PinModal extends Component<Props, State> {
passphraseRevision: '', passphraseRevision: '',
passphraseFocused: false, passphraseFocused: false,
passphraseRevisionFocused: false, passphraseRevisionFocused: false,
visible: false visible: false,
}) });
raf(() => onPassphraseSubmit(empty ? '' : passphrase)); raf(() => onPassphraseSubmit(empty ? '' : passphrase));
} }
render() { render() {
if (!this.props.modal.opened) return null; if (!this.props.modal.opened) return null;
const { const {
device, device,
} = this.props.modal; } = this.props.modal;
const { const {
deviceLabel, deviceLabel,
singleInput, singleInput,
passphrase, passphrase,
@ -229,9 +227,9 @@ export default class PinModal extends Component<Props, State> {
passphraseRevisionTouched, passphraseRevisionTouched,
} = this.state; } = this.state;
let passphraseInputType: string = visible || (!visible && !passphraseFocused) ? "text" : "password"; let passphraseInputType: string = visible || (!visible && !passphraseFocused) ? 'text' : 'password';
let passphraseRevisionInputType: string = visible || (!visible && !passphraseRevisionFocused) ? "text" : "password"; let passphraseRevisionInputType: string = visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password';
passphraseInputType = passphraseRevisionInputType = "text"; passphraseInputType = passphraseRevisionInputType = 'text';
//let passphraseInputType: string = visible || passphraseFocused ? "text" : "password"; //let passphraseInputType: string = visible || passphraseFocused ? "text" : "password";
//let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password"; //let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password";
@ -246,44 +244,46 @@ export default class PinModal extends Component<Props, State> {
<div className="row"> <div className="row">
<label>Passphrase</label> <label>Passphrase</label>
<input <input
ref={ (element) => { this.passphraseInput = element; } } ref={(element) => { this.passphraseInput = element; }}
onChange={ event => this.onPassphraseChange('passphrase', event.currentTarget.value) } onChange={event => this.onPassphraseChange('passphrase', event.currentTarget.value)}
type={ passphraseInputType } type={passphraseInputType}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
data-lpignore="true" data-lpignore="true"
onFocus={ event => this.onPassphraseFocus('passphrase') } onFocus={event => this.onPassphraseFocus('passphrase')}
onBlur={ event => this.onPassphraseBlur('passphrase') } onBlur={event => this.onPassphraseBlur('passphrase')}
tabIndex="1" /> tabIndex="1"
/>
</div> </div>
{ singleInput ? null : ( { singleInput ? null : (
<div className="row"> <div className="row">
<label>Re-enter passphrase</label> <label>Re-enter passphrase</label>
<input <input
ref={ (element) => { this.passphraseRevisionInput = element; } } ref={(element) => { this.passphraseRevisionInput = element; }}
onChange={ event => this.onPassphraseChange('revision', event.currentTarget.value) } onChange={event => this.onPassphraseChange('revision', event.currentTarget.value)}
type={ passphraseRevisionInputType } type={passphraseRevisionInputType}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
data-lpignore="true" data-lpignore="true"
onFocus={ event => this.onPassphraseFocus('revision') } onFocus={event => this.onPassphraseFocus('revision')}
onBlur={ event => this.onPassphraseBlur('revision') } onBlur={event => this.onPassphraseBlur('revision')}
tabIndex="2" /> tabIndex="2"
/>
{ !match && passphraseRevisionTouched ? <span className="error">Passphrases do not match</span> : null } { !match && passphraseRevisionTouched ? <span className="error">Passphrases do not match</span> : null }
</div> </div>
) } ) }
<div className="row"> <div className="row">
<label className="custom-checkbox"> <label className="custom-checkbox">
<input type="checkbox" tabIndex="3" onChange={ showPassphraseCheckboxFn } checked={ visible } /> <input type="checkbox" tabIndex="3" onChange={showPassphraseCheckboxFn} checked={visible} />
<span className="indicator"></span> <span className="indicator" />
Show passphrase Show passphrase
</label> </label>
{/* <label className="custom-checkbox"> {/* <label className="custom-checkbox">
@ -294,12 +294,12 @@ export default class PinModal extends Component<Props, State> {
</div> </div>
<div> <div>
<button type="button" className="submit" tabIndex="4" disabled={ !match } onClick={ event => this.submit() }>Enter</button> <button type="button" className="submit" tabIndex="4" disabled={!match} onClick={event => this.submit()}>Enter</button>
</div> </div>
<div> <div>
<p>If you want to access your default account</p> <p>If you want to access your default account</p>
<p><a className="green" onClick={ event => this.submit(true) }>Leave passphrase blank</a></p> <p><a className="green" onClick={event => this.submit(true)}>Leave passphrase blank</a></p>
</div> </div>
</div> </div>

@ -1,11 +1,10 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import type { Props } from './index'; import type { Props } from './index';
const Confirmation = (props: Props) => { const Confirmation = (props: Props) => {
if (!props.modal.opened) return null; if (!props.modal.opened) return null;
const { device } = props.modal; const { device } = props.modal;
@ -16,6 +15,6 @@ const Confirmation = (props: Props) => {
</div> </div>
</div> </div>
); );
} };
export default Confirmation; export default Confirmation;

@ -1,16 +1,17 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import type { Props } from './index'; import type { Props } from './index';
type State = { type State = {
pin: string; pin: string;
} }
export default class Pin extends Component<Props, State> { export default class Pin extends Component<Props, State> {
keyboardHandler: (event: KeyboardEvent) => void; keyboardHandler: (event: KeyboardEvent) => void;
state: State; state: State;
constructor(props: Props) { constructor(props: Props) {
@ -18,7 +19,7 @@ export default class Pin extends Component<Props, State> {
this.state = { this.state = {
pin: '', pin: '',
} };
} }
onPinAdd = (input: number): void => { onPinAdd = (input: number): void => {
@ -26,7 +27,7 @@ export default class Pin extends Component<Props, State> {
if (pin.length < 9) { if (pin.length < 9) {
pin += input; pin += input;
this.setState({ this.setState({
pin: pin pin,
}); });
} }
} }
@ -43,56 +44,55 @@ export default class Pin extends Component<Props, State> {
event.preventDefault(); event.preventDefault();
switch (event.keyCode) { switch (event.keyCode) {
case 13 : case 13:
// enter, // enter,
onPinSubmit(pin); onPinSubmit(pin);
break; break;
// backspace // backspace
case 8 : case 8:
this.onPinBackspace(); this.onPinBackspace();
break; break;
// numeric and numpad // numeric and numpad
case 49 : case 49:
case 97 : case 97:
this.onPinAdd(1); this.onPinAdd(1);
break; break;
case 50 : case 50:
case 98 : case 98:
this.onPinAdd(2); this.onPinAdd(2);
break; break;
case 51 : case 51:
case 99 : case 99:
this.onPinAdd(3); this.onPinAdd(3);
break; break;
case 52 : case 52:
case 100 : case 100:
this.onPinAdd(4); this.onPinAdd(4);
break; break;
case 53 : case 53:
case 101 : case 101:
this.onPinAdd(5); this.onPinAdd(5);
break; break;
case 54 : case 54:
case 102 : case 102:
this.onPinAdd(6); this.onPinAdd(6);
break; break;
case 55 : case 55:
case 103 : case 103:
this.onPinAdd(7); this.onPinAdd(7);
break; break;
case 56 : case 56:
case 104 : case 104:
this.onPinAdd(8); this.onPinAdd(8);
break; break;
case 57 : case 57:
case 105 : case 105:
this.onPinAdd(9); this.onPinAdd(9);
break; break;
} }
} }
componentWillMount(): void { componentWillMount(): void {
this.keyboardHandler = this.keyboardHandler.bind(this); this.keyboardHandler = this.keyboardHandler.bind(this);
@ -104,13 +104,12 @@ export default class Pin extends Component<Props, State> {
} }
render() { render() {
if (!this.props.modal.opened) return null; if (!this.props.modal.opened) return null;
const { onPinSubmit } = this.props.modalActions; const { onPinSubmit } = this.props.modalActions;
const { device } = this.props.modal; const { device } = this.props.modal;
const { pin } = this.state; const { pin } = this.state;
return ( return (
<div className="pin"> <div className="pin">
{/* <button className="close-modal transparent"></button> */} {/* <button className="close-modal transparent"></button> */}
@ -119,26 +118,26 @@ export default class Pin extends Component<Props, State> {
<div className="pin-input-row"> <div className="pin-input-row">
<input type="password" autoComplete="off" maxLength="9" disabled value={pin} /> <input type="password" autoComplete="off" maxLength="9" disabled value={pin} />
<button type="button" className="pin-backspace transparent" onClick={ event => this.onPinBackspace() }></button> <button type="button" className="pin-backspace transparent" onClick={event => this.onPinBackspace()} />
</div> </div>
<div className="pin-row"> <div className="pin-row">
<button type="button" data-value="7" onClick={ event => this.onPinAdd(7) }>&#8226;</button> <button type="button" data-value="7" onClick={event => this.onPinAdd(7)}>&#8226;</button>
<button type="button" data-value="8" onClick={ event => this.onPinAdd(8) }>&#8226;</button> <button type="button" data-value="8" onClick={event => this.onPinAdd(8)}>&#8226;</button>
<button type="button" data-value="9" onClick={ event => this.onPinAdd(9) }>&#8226;</button> <button type="button" data-value="9" onClick={event => this.onPinAdd(9)}>&#8226;</button>
</div> </div>
<div className="pin-row"> <div className="pin-row">
<button type="button" data-value="4" onClick={ event => this.onPinAdd(4) }>&#8226;</button> <button type="button" data-value="4" onClick={event => this.onPinAdd(4)}>&#8226;</button>
<button type="button" data-value="5" onClick={ event => this.onPinAdd(5) }>&#8226;</button> <button type="button" data-value="5" onClick={event => this.onPinAdd(5)}>&#8226;</button>
<button type="button" data-value="6" onClick={ event => this.onPinAdd(6) }>&#8226;</button> <button type="button" data-value="6" onClick={event => this.onPinAdd(6)}>&#8226;</button>
</div> </div>
<div className="pin-row"> <div className="pin-row">
<button type="button" data-value="1" onClick={ event => this.onPinAdd(1) }>&#8226;</button> <button type="button" data-value="1" onClick={event => this.onPinAdd(1)}>&#8226;</button>
<button type="button" data-value="2" onClick={ event => this.onPinAdd(2) }>&#8226;</button> <button type="button" data-value="2" onClick={event => this.onPinAdd(2)}>&#8226;</button>
<button type="button" data-value="3" onClick={ event => this.onPinAdd(3) }>&#8226;</button> <button type="button" data-value="3" onClick={event => this.onPinAdd(3)}>&#8226;</button>
</div> </div>
<div><button className="submit" type="button" onClick={ event => onPinSubmit(pin) }>Enter PIN</button></div> <div><button className="submit" type="button" onClick={event => onPinSubmit(pin)}>Enter PIN</button></div>
<p>Not sure how PIN works? <a className="green" href="http://doc.satoshilabs.com/trezor-user/enteringyourpin.html" target="_blank" rel="noreferrer noopener">Learn more</a></p> <p>Not sure how PIN works? <a className="green" href="http://doc.satoshilabs.com/trezor-user/enteringyourpin.html" target="_blank" rel="noreferrer noopener">Learn more</a></p>
</div> </div>
); );

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Loader from '../common/LoaderCircle'; import Loader from '../common/LoaderCircle';
@ -12,8 +12,8 @@ type State = {
} }
export default class RememberDevice extends Component<Props, State> { export default class RememberDevice extends Component<Props, State> {
keyboardHandler: (event: KeyboardEvent) => void; keyboardHandler: (event: KeyboardEvent) => void;
state: State; state: State;
constructor(props: Props) { constructor(props: Props) {
@ -21,7 +21,7 @@ export default class RememberDevice extends Component<Props, State> {
this.state = { this.state = {
countdown: 10, countdown: 10,
} };
} }
keyboardHandler(event: KeyboardEvent): void { keyboardHandler(event: KeyboardEvent): void {
@ -32,10 +32,9 @@ export default class RememberDevice extends Component<Props, State> {
} }
componentDidMount(): void { componentDidMount(): void {
const ticker = () => { const ticker = () => {
if (this.state.countdown - 1 <= 0) { if (this.state.countdown - 1 <= 0) {
// TODO: possible race condition, // TODO: possible race condition,
// device could be already connected but it didn't emit Device.CONNECT event yet // device could be already connected but it didn't emit Device.CONNECT event yet
window.clearInterval(this.state.ticker); window.clearInterval(this.state.ticker);
if (this.props.modal.opened) { if (this.props.modal.opened) {
@ -43,14 +42,14 @@ export default class RememberDevice extends Component<Props, State> {
} }
} else { } else {
this.setState({ this.setState({
countdown: this.state.countdown - 1 countdown: this.state.countdown - 1,
}); });
} }
} };
this.setState({ this.setState({
countdown: 10, countdown: 10,
ticker: window.setInterval(ticker, 1000) ticker: window.setInterval(ticker, 1000),
}); });
this.keyboardHandler = this.keyboardHandler.bind(this); this.keyboardHandler = this.keyboardHandler.bind(this);
@ -76,13 +75,13 @@ export default class RememberDevice extends Component<Props, State> {
const { onForgetDevice, onRememberDevice } = this.props.modalActions; const { onForgetDevice, onRememberDevice } = this.props.modalActions;
let label = device.label; let label = device.label;
const devicePlural: string = instances && instances.length > 1 ? "devices or to remember them" : "device or to remember it"; const devicePlural: string = instances && instances.length > 1 ? 'devices or to remember them' : 'device or to remember it';
if (instances && instances.length > 0) { if (instances && instances.length > 0) {
label = instances.map((instance, index) => { label = instances.map((instance, index) => {
let comma: string = ''; let comma: string = '';
if (index > 0) comma = ', '; if (index > 0) comma = ', ';
return ( return (
<span key={ index }>{ comma }{ instance.instanceLabel }</span> <span key={index}>{ comma }{ instance.instanceLabel }</span>
); );
}); });
} }
@ -90,15 +89,14 @@ export default class RememberDevice extends Component<Props, State> {
<div className="remember"> <div className="remember">
<h3>Forget {label}?</h3> <h3>Forget {label}?</h3>
<p>Would you like TREZOR Wallet to forget your { devicePlural }, so that it is still visible even while disconnected?</p> <p>Would you like TREZOR Wallet to forget your { devicePlural }, so that it is still visible even while disconnected?</p>
<button onClick={ event => this.forget() }><span>Forget <Loader size="28" label={ this.state.countdown.toString() } /></span></button> <button onClick={event => this.forget()}><span>Forget <Loader size="28" label={this.state.countdown.toString()} /></span></button>
<button className="white" onClick={ event => onRememberDevice(device) }>Remember</button> <button className="white" onClick={event => onRememberDevice(device)}>Remember</button>
</div> </div>
); );
} }
} }
export class ForgetDevice extends Component<Props> { export class ForgetDevice extends Component<Props> {
keyboardHandler: (event: KeyboardEvent) => void; keyboardHandler: (event: KeyboardEvent) => void;
keyboardHandler(event: KeyboardEvent): void { keyboardHandler(event: KeyboardEvent): void {
@ -131,8 +129,8 @@ export class ForgetDevice extends Component<Props> {
<div className="remember"> <div className="remember">
<h3>Forget { device.instanceLabel } ?</h3> <h3>Forget { device.instanceLabel } ?</h3>
<p>Forgetting only removes the device from the list on the left, your coins are still safe and you can access them by reconnecting your TREZOR again.</p> <p>Forgetting only removes the device from the list on the left, your coins are still safe and you can access them by reconnecting your TREZOR again.</p>
<button onClick={ event => this.forget() }>Forget</button> <button onClick={event => this.forget()}>Forget</button>
<button className="white" onClick={ onCancel }>Don't forget</button> <button className="white" onClick={onCancel}>Don't forget</button>
</div> </div>
); );
} }

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -55,55 +55,55 @@ const duration = 300;
const Fade = ({ children, ...props }) => ( const Fade = ({ children, ...props }) => (
<CSSTransition <CSSTransition
{ ...props } {...props}
timeout={ 1000 } timeout={1000}
classNames="fade"> classNames="fade"
{ children } >
{ children }
</CSSTransition> </CSSTransition>
); );
class Modal extends Component<Props> { class Modal extends Component<Props> {
render() { render() {
if (!this.props.modal.opened) return null; if (!this.props.modal.opened) return null;
const { opened, windowType } = this.props.modal; const { opened, windowType } = this.props.modal;
let component = null; let component = null;
switch (windowType) { switch (windowType) {
case UI.REQUEST_PIN : case UI.REQUEST_PIN:
component = (<Pin { ...this.props } />); component = (<Pin {...this.props} />);
break; break;
case UI.INVALID_PIN : case UI.INVALID_PIN:
component = (<InvalidPin { ...this.props } />); component = (<InvalidPin {...this.props} />);
break; break;
case UI.REQUEST_PASSPHRASE : case UI.REQUEST_PASSPHRASE:
component = (<Passphrase { ...this.props } />); component = (<Passphrase {...this.props} />);
break; break;
case "ButtonRequest_SignTx" : case 'ButtonRequest_SignTx':
component = (<ConfirmSignTx { ...this.props } />) component = (<ConfirmSignTx {...this.props} />);
break; break;
// case "ButtonRequest_Address" : // case "ButtonRequest_Address" :
// component = (<ConfirmAddress { ...this.props } />) // component = (<ConfirmAddress { ...this.props } />)
// break; // break;
case "ButtonRequest_PassphraseType" : case 'ButtonRequest_PassphraseType':
component = (<PassphraseType { ...this.props } />) component = (<PassphraseType {...this.props} />);
break; break;
case RECEIVE.REQUEST_UNVERIFIED : case RECEIVE.REQUEST_UNVERIFIED:
component = (<ConfirmUnverifiedAddress { ...this.props } />) component = (<ConfirmUnverifiedAddress {...this.props} />);
break; break;
case CONNECT.REMEMBER_REQUEST : case CONNECT.REMEMBER_REQUEST:
component = (<RememberDevice { ...this.props } />) component = (<RememberDevice {...this.props} />);
break; break;
case CONNECT.FORGET_REQUEST : case CONNECT.FORGET_REQUEST:
component = (<ForgetDevice { ...this.props } />) component = (<ForgetDevice {...this.props} />);
break; break;
case CONNECT.TRY_TO_DUPLICATE : case CONNECT.TRY_TO_DUPLICATE:
component = (<DuplicateDevice { ...this.props } />) component = (<DuplicateDevice {...this.props} />);
break; break;
} }
let ch = null; let ch = null;
@ -123,28 +123,24 @@ class Modal extends Component<Props> {
} }
} }
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
return { modal: state.modal,
modal: state.modal, accounts: state.accounts,
accounts: state.accounts, devices: state.devices,
devices: state.devices, connect: state.connect,
connect: state.connect, selectedAccount: state.selectedAccount,
selectedAccount: state.selectedAccount, sendForm: state.sendForm,
sendForm: state.sendForm, receive: state.receive,
receive: state.receive, localStorage: state.localStorage,
localStorage: state.localStorage, wallet: state.wallet,
wallet: state.wallet });
};
} const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
modalActions: bindActionCreators(ModalActions, dispatch),
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { receiveActions: bindActionCreators(ReceiveActions, dispatch),
return { });
modalActions: bindActionCreators(ModalActions, dispatch),
receiveActions: bindActionCreators(ReceiveActions, dispatch),
};
}
// export default connect(mapStateToProps, mapDispatchToProps)(Modal); // export default connect(mapStateToProps, mapDispatchToProps)(Modal);
export default withRouter( export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Modal) connect(mapStateToProps, mapDispatchToProps)(Modal),
); );

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
@ -16,8 +16,8 @@ type State = {
} }
class Indicator extends Component<Props, State> { class Indicator extends Component<Props, State> {
reposition: () => void; reposition: () => void;
state: State; state: State;
constructor(props: Props) { constructor(props: Props) {
@ -26,9 +26,9 @@ class Indicator extends Component<Props, State> {
this.state = { this.state = {
style: { style: {
width: 0, width: 0,
left: 0 left: 0,
}, },
} };
this.reposition = this.reposition.bind(this); this.reposition = this.reposition.bind(this);
} }
@ -63,21 +63,20 @@ class Indicator extends Component<Props, State> {
this.setState({ this.setState({
style: { style: {
width: bounds.width, width: bounds.width,
left: left, left,
} },
}) });
} }
} }
render() { render() {
return ( return (
<div className="indicator" style={ this.state.style }>{ this.props.pathname }</div> <div className="indicator" style={this.state.style}>{ this.props.pathname }</div>
); );
} }
} }
const AccountTabs = (props: any) => { const AccountTabs = (props: any) => {
const urlParams = props.match.params; const urlParams = props.match.params;
const basePath = `/device/${urlParams.device}/network/${urlParams.network}/account/${urlParams.account}`; const basePath = `/device/${urlParams.device}/network/${urlParams.network}/account/${urlParams.account}`;
@ -86,21 +85,21 @@ const AccountTabs = (props: any) => {
{/* <NavLink to={ `${basePath}` }> {/* <NavLink to={ `${basePath}` }>
History History
</NavLink> */} </NavLink> */}
<NavLink exact to={ `${basePath}` }> <NavLink exact to={`${basePath}`}>
Summary Summary
</NavLink> </NavLink>
<NavLink to={ `${basePath}/send` }> <NavLink to={`${basePath}/send`}>
Send Send
</NavLink> </NavLink>
<NavLink to={ `${basePath}/receive` }> <NavLink to={`${basePath}/receive`}>
Receive Receive
</NavLink> </NavLink>
{/* <NavLink to={ `${basePath}/signverify` }> {/* <NavLink to={ `${basePath}/signverify` }>
Sign &amp; Verify Sign &amp; Verify
</NavLink> */} </NavLink> */}
<Indicator pathname={props.match.pathname } /> <Indicator pathname={props.match.pathname} />
</div> </div>
); );
} };
export default AccountTabs; export default AccountTabs;

@ -1,10 +1,12 @@
/* @flow */ /* @flow */
'use strict';
import * as React from 'react'; import * as React from 'react';
import { Notification } from '~/js/components/common/Notification'; import { Notification } from '~/js/components/common/Notification';
import type { State, TrezorDevice, Action, ThunkAction } from '~/flowtype'; import type {
State, TrezorDevice, Action, ThunkAction,
} from '~/flowtype';
import type { Account } from '~/js/reducers/AccountsReducer'; import type { Account } from '~/js/reducers/AccountsReducer';
import type { Discovery } from '~/js/reducers/DiscoveryReducer'; import type { Discovery } from '~/js/reducers/DiscoveryReducer';
@ -16,7 +18,7 @@ export type StateProps = {
} }
export type DispatchProps = { export type DispatchProps = {
} }
export type Props = StateProps & DispatchProps; export type Props = StateProps & DispatchProps;
@ -31,7 +33,7 @@ const SelectedAccount = (props: Props) => {
const { const {
account, account,
discovery discovery,
} = accountState; } = accountState;
// account not found (yet). checking why... // account not found (yet). checking why...
@ -45,73 +47,68 @@ const SelectedAccount = (props: Props) => {
<Notification className="info" title="Loading accounts..." /> <Notification className="info" title="Loading accounts..." />
</section> </section>
); );
} else {
// case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
return (
<section>
<Notification
className="info"
title={ `Device ${ device.instanceLabel } is unavailable` }
message="Change passphrase settings to use this device"
/>
</section>
);
} }
} else { // case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
// case 3: device is disconnected
return ( return (
<section> <section>
<Notification <Notification
className="info" className="info"
title={ `Device ${ device.instanceLabel } is disconnected` } title={`Device ${device.instanceLabel} is unavailable`}
message="Connect device to load accounts" message="Change passphrase settings to use this device"
/> />
</section> </section>
); );
} }
} else if (discovery.waitingForBackend) { // case 3: device is disconnected
// case 4: backend is not working
return ( return (
<section> <section>
<Notification className="warning" title="Backend not working" /> <Notification
className="info"
title={`Device ${device.instanceLabel} is disconnected`}
message="Connect device to load accounts"
/>
</section> </section>
); );
} else if (discovery.completed) { } if (discovery.waitingForBackend) {
// case 5: account not found and discovery is completed // case 4: backend is not working
return ( return (
<section> <section>
<Notification className="warning" title="Account does not exist" /> <Notification className="warning" title="Backend not working" />
</section> </section>
); );
} else { } if (discovery.completed) {
// case 6: discovery is not completed yet // case 5: account not found and discovery is completed
return ( return (
<section> <section>
<Notification className="info" title="Loading accounts..." /> <Notification className="warning" title="Account does not exist" />
</section> </section>
); );
} }
} else { // case 6: discovery is not completed yet
let notification: ?React$Element<typeof Notification> = null;
if (!device.connected) {
notification = <Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />;
} else if (!device.available) {
notification = <Notification className="info" title={ `Device ${ device.instanceLabel } is unavailable` } message="Change passphrase settings to use this device" />;
}
if (discovery && !discovery.completed && !notification) {
notification = <Notification className="info" title="Loading accounts..." />;
}
return ( return (
<section className={ props.className }> <section>
{ notification } <Notification className="info" title="Loading accounts..." />
{ props.children }
</section> </section>
) );
} }
} let notification: ?React$Element<typeof Notification> = null;
if (!device.connected) {
notification = <Notification className="info" title={`Device ${device.instanceLabel} is disconnected`} />;
} else if (!device.available) {
notification = <Notification className="info" title={`Device ${device.instanceLabel} is unavailable`} message="Change passphrase settings to use this device" />;
}
if (discovery && !discovery.completed && !notification) {
notification = <Notification className="info" title="Loading accounts..." />;
}
return (
<section className={props.className}>
{ notification }
{ props.children }
</section>
);
};
export default SelectedAccount; export default SelectedAccount;

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
@ -12,9 +12,7 @@ import { Notification } from '~/js/components/common/Notification';
import type { Props } from './index'; import type { Props } from './index';
const Receive = (props: Props) => { const Receive = (props: Props) => {
const device = props.wallet.selectedDevice; const device = props.wallet.selectedDevice;
const { const {
account, account,
@ -33,7 +31,7 @@ const Receive = (props: Props) => {
let address = `${account.address.substring(0, 20)}...`; let address = `${account.address.substring(0, 20)}...`;
let className = 'address hidden'; let className = 'address hidden';
let button = ( let button = (
<button disabled={ device.connected && !discovery.completed } onClick={ event => props.showAddress(account.addressPath) }> <button disabled={device.connected && !discovery.completed} onClick={event => props.showAddress(account.addressPath)}>
<span>Show full address</span> <span>Show full address</span>
</button> </button>
); );
@ -46,42 +44,42 @@ const Receive = (props: Props) => {
fgColor="#000000" fgColor="#000000"
level="Q" level="Q"
style={{ width: 256 }} style={{ width: 256 }}
value={ account.address } value={account.address}
/> />
); );
address = account.address; address = account.address;
className = addressUnverified ? 'address unverified' : 'address'; className = addressUnverified ? 'address unverified' : 'address';
const tooltip = addressUnverified ? const tooltip = addressUnverified
(<div>Unverified address.<br/>{ device.connected && device.available ? 'Show on TREZOR' : 'Connect your TREZOR to verify it.' }</div>) ? (<div>Unverified address.<br />{ device.connected && device.available ? 'Show on TREZOR' : 'Connect your TREZOR to verify it.' }</div>)
: : (<div>{ device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify address.' }</div>);
(<div>{ device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify address.' }</div>);
button = ( button = (
<Tooltip <Tooltip
arrowContent={<div className="rc-tooltip-arrow-inner"></div>} arrowContent={<div className="rc-tooltip-arrow-inner" />}
overlay={ tooltip } overlay={tooltip}
placement="bottomRight"> placement="bottomRight"
<button className="white" onClick={ event => props.showAddress(account.addressPath) }> >
<span></span> <button className="white" onClick={event => props.showAddress(account.addressPath)}>
<span />
</button> </button>
</Tooltip> </Tooltip>
); );
} }
let ver = null; const ver = null;
if (props.modal.opened && props.modal.windowType === 'ButtonRequest_Address') { if (props.modal.opened && props.modal.windowType === 'ButtonRequest_Address') {
className = 'address verifying'; className = 'address verifying';
address = account.address; address = account.address;
// ver = (<div className="confirm">Confirm address on TREZOR</div>) // ver = (<div className="confirm">Confirm address on TREZOR</div>)
button = (<div className="confirm">{ account.network } account #{ (account.index + 1) }</div>); button = (<div className="confirm">{ account.network } account #{ (account.index + 1) }</div>);
} }
return ( return (
<div> <div>
<h2>Receive Ethereum or tokens</h2> <h2>Receive Ethereum or tokens</h2>
<div className={ className }> <div className={className}>
{ ver } { ver }
<div className="value"> <div className="value">
{ address } { address }
@ -91,12 +89,10 @@ const Receive = (props: Props) => {
{ qrCode } { qrCode }
</div> </div>
); );
} };
export default (props: Props) => { export default (props: Props) => (
return ( <SelectedAccount {...props}>
<SelectedAccount { ...props }> <Receive {...props} />
<Receive { ...props} /> </SelectedAccount>
</SelectedAccount> );
);
}

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -7,13 +7,13 @@ import { connect } from 'react-redux';
import { default as ReceiveActions } from '~/js/actions/ReceiveActions'; import { default as ReceiveActions } from '~/js/actions/ReceiveActions';
import * as TokenActions from '~/js/actions/TokenActions'; import * as TokenActions from '~/js/actions/TokenActions';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import Receive from './Receive'; import Receive from './Receive';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State, Dispatch } from '~/flowtype'; import type { State, Dispatch } from '~/flowtype';
import type { import type {
StateProps as BaseStateProps, StateProps as BaseStateProps,
DispatchProps as BaseDispatchProps DispatchProps as BaseDispatchProps,
} from '../SelectedAccount'; } from '../SelectedAccount';
type OwnProps = { } type OwnProps = { }
@ -29,21 +29,17 @@ type DispatchProps = BaseDispatchProps & {
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps; export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
return { className: 'receive',
className: "receive", selectedAccount: state.selectedAccount,
selectedAccount: state.selectedAccount, wallet: state.wallet,
wallet: state.wallet,
receive: state.receive, receive: state.receive,
modal: state.modal, modal: state.modal,
}; });
}
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
return { showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch),
showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch), });
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Receive); export default connect(mapStateToProps, mapDispatchToProps)(Receive);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import Tooltip from 'rc-tooltip'; import Tooltip from 'rc-tooltip';
@ -13,13 +13,12 @@ type Props = {
}; };
const AdvancedForm = (props: Props) => { const AdvancedForm = (props: Props) => {
const {
const {
account, account,
network network,
} = props.selectedAccount; } = props.selectedAccount;
const { const {
networkSymbol, networkSymbol,
currency, currency,
gasPrice, gasPrice,
@ -31,7 +30,7 @@ const AdvancedForm = (props: Props) => {
errors, errors,
warnings, warnings,
infos, infos,
advanced advanced,
} = props.sendForm; } = props.sendForm;
const { const {
@ -39,19 +38,21 @@ const AdvancedForm = (props: Props) => {
onGasPriceChange, onGasPriceChange,
onGasLimitChange, onGasLimitChange,
onNonceChange, onNonceChange,
onDataChange onDataChange,
} = props.sendFormActions; } = props.sendFormActions;
if (!advanced) return ( if (!advanced) {
<div className="advanced-container"> return (
<a className="advanced" onClick={ toggleAdvanced }>Advanced settings</a> <div className="advanced-container">
{ props.children } <a className="advanced" onClick={toggleAdvanced}>Advanced settings</a>
</div> { props.children }
); </div>
);
}
const nonceTooltip = ( const nonceTooltip = (
<div className="tooltip-wrapper"> <div className="tooltip-wrapper">
TODO<br/> TODO<br />
</div> </div>
); );
@ -67,18 +68,18 @@ const AdvancedForm = (props: Props) => {
const gasLimitTooltip = ( const gasLimitTooltip = (
<div className="tooltip-wrapper"> <div className="tooltip-wrapper">
Gas limit is the amount of gas to send with your transaction.<br/> Gas limit is the amount of gas to send with your transaction.<br />
<span>TX fee = gas price * gas limit</span> &amp; is paid to miners for including your TX in a block.<br/> <span>TX fee = gas price * gas limit</span> &amp; is paid to miners for including your TX in a block.<br />
Increasing this number will not get your TX mined faster.<br/> Increasing this number will not get your TX mined faster.<br />
Default value for sending { gasLimitTooltipCurrency } is <span>{ gasLimitTooltipValue }</span> Default value for sending { gasLimitTooltipCurrency } is <span>{ gasLimitTooltipValue }</span>
</div> </div>
); );
const gasPriceTooltip = ( const gasPriceTooltip = (
<div className="tooltip-wrapper"> <div className="tooltip-wrapper">
Gas Price is the amount you pay per unit of gas.<br/> Gas Price is the amount you pay per unit of gas.<br />
<span>TX fee = gas price * gas limit</span> &amp; is paid to miners for including your TX in a block.<br/> <span>TX fee = gas price * gas limit</span> &amp; is paid to miners for including your TX in a block.<br />
Higher the gas price = faster transaction, but more expensive. Recommended is <span>{ recommendedGasPrice } GWEI.</span><br/> Higher the gas price = faster transaction, but more expensive. Recommended is <span>{ recommendedGasPrice } GWEI.</span><br />
<a className="green" href="https://myetherwallet.github.io/knowledge-base/gas/what-is-gas-ethereum.html" target="_blank" rel="noreferrer noopener">Read more</a> <a className="green" href="https://myetherwallet.github.io/knowledge-base/gas/what-is-gas-ethereum.html" target="_blank" rel="noreferrer noopener">Read more</a>
</div> </div>
); );
@ -90,10 +91,9 @@ const AdvancedForm = (props: Props) => {
); );
return ( return (
<div className="advanced-container opened"> <div className="advanced-container opened">
<a className="advanced" onClick={ toggleAdvanced }>Advanced settings</a> <a className="advanced" onClick={toggleAdvanced}>Advanced settings</a>
<div className="row gas-row"> <div className="row gas-row">
{/* <div className="column nonce"> {/* <div className="column nonce">
<label> <label>
@ -105,7 +105,7 @@ const AdvancedForm = (props: Props) => {
<span className="what-is-it"></span> <span className="what-is-it"></span>
</Tooltip> </Tooltip>
</label> </label>
<input <input
type="text" type="text"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
@ -118,45 +118,49 @@ const AdvancedForm = (props: Props) => {
</div> */} </div> */}
<div className="column"> <div className="column">
<label> <label>
Gas limit Gas limit
<Tooltip <Tooltip
arrowContent={<div className="rc-tooltip-arrow-inner"></div>} arrowContent={<div className="rc-tooltip-arrow-inner" />}
overlay={ gasLimitTooltip } overlay={gasLimitTooltip}
placement="top"> placement="top"
<span className="what-is-it"></span> >
<span className="what-is-it" />
</Tooltip> </Tooltip>
</label> </label>
<input <input
type="text" type="text"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
value={ gasLimit } value={gasLimit}
disabled={ networkSymbol === currency && data.length > 0 } disabled={networkSymbol === currency && data.length > 0}
onChange={ event => onGasLimitChange(event.target.value) } /> onChange={event => onGasLimitChange(event.target.value)}
/>
{ errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null } { errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null }
{ warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null } { warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null }
{ calculatingGasLimit ? (<span className="info">Calculating...</span>) : null } { calculatingGasLimit ? (<span className="info">Calculating...</span>) : null }
</div> </div>
<div className="column"> <div className="column">
<label> <label>
Gas price Gas price
<Tooltip <Tooltip
arrowContent={<div className="rc-tooltip-arrow-inner"></div>} arrowContent={<div className="rc-tooltip-arrow-inner" />}
overlay={ gasPriceTooltip } overlay={gasPriceTooltip}
placement="top"> placement="top"
<span className="what-is-it"></span> >
<span className="what-is-it" />
</Tooltip> </Tooltip>
</label> </label>
<input <input
type="text" type="text"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
value={ gasPrice } value={gasPrice}
onChange={ event => onGasPriceChange(event.target.value) } /> onChange={event => onGasPriceChange(event.target.value)}
/>
{ errors.gasPrice ? (<span className="error">{ errors.gasPrice }</span>) : null } { errors.gasPrice ? (<span className="error">{ errors.gasPrice }</span>) : null }
</div> </div>
</div> </div>
@ -165,13 +169,14 @@ const AdvancedForm = (props: Props) => {
<label> <label>
Data Data
<Tooltip <Tooltip
arrowContent={<div className="rc-tooltip-arrow-inner"></div>} arrowContent={<div className="rc-tooltip-arrow-inner" />}
overlay={ dataTooltip } overlay={dataTooltip}
placement="top"> placement="top"
<span className="what-is-it"></span> >
<span className="what-is-it" />
</Tooltip> </Tooltip>
</label> </label>
<textarea disabled={ networkSymbol !== currency } value={ networkSymbol !== currency ? '' : data } onChange={ event => onDataChange(event.target.value) }></textarea> <textarea disabled={networkSymbol !== currency} value={networkSymbol !== currency ? '' : data} onChange={event => onDataChange(event.target.value)} />
{ errors.data ? (<span className="error">{ errors.data }</span>) : null } { errors.data ? (<span className="error">{ errors.data }</span>) : null }
</div> </div>
@ -180,7 +185,7 @@ const AdvancedForm = (props: Props) => {
</div> </div>
</div> </div>
) );
} };
export default AdvancedForm; export default AdvancedForm;

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import * as React from 'react'; import * as React from 'react';
@ -37,12 +37,14 @@ export default class CoinSelectOption extends React.Component<Props> {
render() { render() {
const css = `${this.props.className} ${this.props.option.value}`; const css = `${this.props.className} ${this.props.option.value}`;
return ( return (
<div className={ css } <div
onMouseDown={ this.handleMouseDown.bind(this) } className={css}
onMouseEnter={ this.handleMouseEnter.bind(this) } onMouseDown={this.handleMouseDown.bind(this)}
onMouseMove={ this.handleMouseMove.bind(this) } onMouseEnter={this.handleMouseEnter.bind(this)}
title={ this.props.option.label }> onMouseMove={this.handleMouseMove.bind(this)}
<span>{ this.props.children }</span> title={this.props.option.label}
>
<span>{ this.props.children }</span>
</div> </div>
); );
} }

@ -1,20 +1,18 @@
/* @flow */ /* @flow */
'use strict';
import * as React from 'react'; import * as React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
export const FeeSelectValue = (props: any): any => { export const FeeSelectValue = (props: any): any => (
return ( <div>
<div> <div className="Select-value fee-option">
<div className="Select-value fee-option"> <span className="fee-value">{ props.value.value }</span>
<span className="fee-value">{ props.value.value }</span> <span className="fee-label">{ props.value.label }</span>
<span className="fee-label">{ props.value.label }</span>
</div>
</div> </div>
); </div>
} );
type Props = { type Props = {
children: React.Node, children: React.Node,
@ -49,12 +47,14 @@ export class FeeSelectOption extends React.Component<Props> {
render() { render() {
return ( return (
<div className={ this.props.className } <div
onMouseDown={ this.handleMouseDown.bind(this) } className={this.props.className}
onMouseEnter={ this.handleMouseEnter.bind(this) } onMouseDown={this.handleMouseDown.bind(this)}
onMouseMove={ this.handleMouseMove.bind(this) }> onMouseEnter={this.handleMouseEnter.bind(this)}
<span className="fee-value">{ this.props.option.value }</span> onMouseMove={this.handleMouseMove.bind(this)}
<span className="fee-label">{ this.props.option.label }</span> >
<span className="fee-value">{ this.props.option.value }</span>
<span className="fee-label">{ this.props.option.label }</span>
</div> </div>
); );
} }

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import ColorHash from 'color-hash'; import ColorHash from 'color-hash';
@ -24,17 +24,15 @@ type Style = {
} }
const PendingTransactions = (props: Props) => { const PendingTransactions = (props: Props) => {
const pending = props.pending.filter(tx => !tx.rejected); const pending = props.pending.filter(tx => !tx.rejected);
if (pending.length < 1) return null; if (pending.length < 1) return null;
const tokens: Array<Token> = props.tokens; const tokens: Array<Token> = props.tokens;
const bgColorFactory = new ColorHash({lightness: 0.7}); const bgColorFactory = new ColorHash({ lightness: 0.7 });
const textColorFactory = new ColorHash(); const textColorFactory = new ColorHash();
const pendingTxs: React$Element<string> = pending.map((tx, i) => { const pendingTxs: React$Element<string> = pending.map((tx, i) => {
let iconColor: Style; let iconColor: Style;
let symbol: string; let symbol: string;
let name: string; let name: string;
@ -45,17 +43,17 @@ const PendingTransactions = (props: Props) => {
iconColor = { iconColor = {
color: '#ffffff', color: '#ffffff',
background: '#000000', background: '#000000',
borderColor: '#000000' borderColor: '#000000',
} };
symbol = "Unknown"; symbol = 'Unknown';
name = "Unknown"; name = 'Unknown';
} else { } else {
const bgColor: string = bgColorFactory.hex(token.name); const bgColor: string = bgColorFactory.hex(token.name);
iconColor = { iconColor = {
color: textColorFactory.hex(token.name), color: textColorFactory.hex(token.name),
background: bgColor, background: bgColor,
borderColor: bgColor borderColor: bgColor,
} };
symbol = token.symbol.toUpperCase(); symbol = token.symbol.toUpperCase();
name = token.name; name = token.name;
} }
@ -63,25 +61,25 @@ const PendingTransactions = (props: Props) => {
iconColor = { iconColor = {
color: textColorFactory.hex(tx.network), color: textColorFactory.hex(tx.network),
background: bgColorFactory.hex(tx.network), background: bgColorFactory.hex(tx.network),
borderColor: bgColorFactory.hex(tx.network) borderColor: bgColorFactory.hex(tx.network),
} };
symbol = props.network.symbol; symbol = props.network.symbol;
name = props.network.name; name = props.network.name;
} }
return ( return (
<div key={i} className="tx"> <div key={i} className="tx">
<div className="icon" style={ iconColor }> <div className="icon" style={iconColor}>
<div className="icon-inner"> <div className="icon-inner">
<ScaleText widthOnly><p>{ symbol }</p></ScaleText> <ScaleText widthOnly><p>{ symbol }</p></ScaleText>
</div> </div>
</div> </div>
<div className="name"> <div className="name">
<a href={ `${props.network.explorer.tx}${tx.id}`} target="_blank" rel="noreferrer noopener">{ name }</a> <a href={`${props.network.explorer.tx}${tx.id}`} target="_blank" rel="noreferrer noopener">{ name }</a>
</div> </div>
<div className="amount">{ isSmartContractTx ? tx.amount : tx.total } { symbol }</div> <div className="amount">{ isSmartContractTx ? tx.amount : tx.total } { symbol }</div>
</div> </div>
) );
}); });
@ -90,7 +88,7 @@ const PendingTransactions = (props: Props) => {
<h2>Pending transactions</h2> <h2>Pending transactions</h2>
{ pendingTxs } { pendingTxs }
</div> </div>
) );
} };
export default PendingTransactions; export default PendingTransactions;

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Select from 'react-select'; import Select from 'react-select';
@ -17,7 +17,6 @@ import type { Token } from '~/flowtype';
export default class SendContainer extends Component<Props> { export default class SendContainer extends Component<Props> {
componentWillReceiveProps(newProps: Props) { componentWillReceiveProps(newProps: Props) {
calculate(this.props, newProps); calculate(this.props, newProps);
validation(newProps); validation(newProps);
@ -27,8 +26,8 @@ export default class SendContainer extends Component<Props> {
render() { render() {
return ( return (
<SelectedAccount { ...this.props }> <SelectedAccount {...this.props}>
<Send { ...this.props} /> <Send {...this.props} />
</SelectedAccount> </SelectedAccount>
); );
} }
@ -36,18 +35,17 @@ export default class SendContainer extends Component<Props> {
const Send = (props: Props) => { const Send = (props: Props) => {
const device = props.wallet.selectedDevice; const device = props.wallet.selectedDevice;
const { const {
account, account,
network, network,
discovery, discovery,
tokens tokens,
} = props.selectedAccount; } = props.selectedAccount;
if (!device || !account || !discovery || !network) return null; if (!device || !account || !discovery || !network) return null;
const { const {
address, address,
amount, amount,
setMax, setMax,
@ -77,9 +75,7 @@ const Send = (props: Props) => {
const fiatRate = props.fiat.find(f => f.network === network); const fiatRate = props.fiat.find(f => f.network === network);
const tokensSelectData = tokens.map(t => { const tokensSelectData = tokens.map(t => ({ value: t.symbol, label: t.symbol }));
return { value: t.symbol, label: t.symbol };
});
tokensSelectData.unshift({ value: network.symbol, label: network.symbol }); tokensSelectData.unshift({ value: network.symbol, label: network.symbol });
const setMaxClassName: string = setMax ? 'set-max enabled' : 'set-max'; const setMaxClassName: string = setMax ? 'set-max enabled' : 'set-max';
@ -87,8 +83,8 @@ const Send = (props: Props) => {
let updateFeeLevelsButton = null; let updateFeeLevelsButton = null;
if (gasPriceNeedsUpdate) { if (gasPriceNeedsUpdate) {
updateFeeLevelsButton = ( updateFeeLevelsButton = (
<span className="update-fee-levels">Recommended fees updated. <a onClick={ updateFeeLevels }>Click here to use them</a></span> <span className="update-fee-levels">Recommended fees updated. <a onClick={updateFeeLevels}>Click here to use them</a></span>
) );
} }
let addressClassName: ?string; let addressClassName: ?string;
@ -103,12 +99,12 @@ const Send = (props: Props) => {
let buttonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending; let buttonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending;
let buttonLabel: string = 'Send'; let buttonLabel: string = 'Send';
if (networkSymbol !== currency && amount.length > 0 && !errors.amount) { if (networkSymbol !== currency && amount.length > 0 && !errors.amount) {
buttonLabel += ` ${amount} ${ currency.toUpperCase() }` buttonLabel += ` ${amount} ${currency.toUpperCase()}`;
} else if (networkSymbol === currency && total !== '0') { } else if (networkSymbol === currency && total !== '0') {
buttonLabel += ` ${total} ${ network.symbol }`; buttonLabel += ` ${total} ${network.symbol}`;
} }
if (!device.connected){ if (!device.connected) {
buttonLabel = 'Device is not connected'; buttonLabel = 'Device is not connected';
buttonDisabled = true; buttonDisabled = true;
} else if (!device.available) { } else if (!device.available) {
@ -118,22 +114,23 @@ const Send = (props: Props) => {
buttonLabel = 'Loading accounts'; buttonLabel = 'Loading accounts';
buttonDisabled = true; buttonDisabled = true;
} }
return ( return (
<section className="send-form"> <section className="send-form">
<h2>Send Ethereum or tokens</h2> <h2>Send Ethereum or tokens</h2>
<div className="row address-input"> <div className="row address-input">
<label>Address</label> <label>Address</label>
<input <input
type="text" type="text"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
value={ address } value={address}
className={ addressClassName } className={addressClassName}
onChange={ event => onAddressChange(event.target.value) } /> onChange={event => onAddressChange(event.target.value)}
<span className="input-icon"></span> />
<span className="input-icon" />
{ errors.address ? (<span className="error">{ errors.address }</span>) : null } { errors.address ? (<span className="error">{ errors.address }</span>) : null }
{ warnings.address ? (<span className="warning">{ warnings.address }</span>) : null } { warnings.address ? (<span className="warning">{ warnings.address }</span>) : null }
{ infos.address ? (<span className="info">{ infos.address }</span>) : null } { infos.address ? (<span className="info">{ infos.address }</span>) : null }
@ -142,28 +139,30 @@ const Send = (props: Props) => {
<div className="row"> <div className="row">
<label>Amount</label> <label>Amount</label>
<div className="amount-input"> <div className="amount-input">
<input <input
type="text" type="text"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
value={ amount } value={amount}
className={ errors.amount ? 'not-valid' : null } className={errors.amount ? 'not-valid' : null}
onChange={ event => onAmountChange(event.target.value) } /> onChange={event => onAmountChange(event.target.value)}
/>
<a className={ setMaxClassName } onClick={ onSetMax }>Set max</a> <a className={setMaxClassName} onClick={onSetMax}>Set max</a>
<Select <Select
name="currency" name="currency"
className="currency" className="currency"
searchable={ false } searchable={false}
clearable= { false } clearable={false}
multi={ false } multi={false}
value={ currency } value={currency}
disabled={ tokensSelectData.length < 2 } disabled={tokensSelectData.length < 2}
onChange={ onCurrencyChange } onChange={onCurrencyChange}
options={ tokensSelectData } /> options={tokensSelectData}
/>
</div> </div>
{ errors.amount ? (<span className="error">{ errors.amount }</span>) : null } { errors.amount ? (<span className="error">{ errors.amount }</span>) : null }
{ warnings.amount ? (<span className="warning">{ warnings.amount }</span>) : null } { warnings.amount ? (<span className="warning">{ warnings.amount }</span>) : null }
@ -171,32 +170,35 @@ const Send = (props: Props) => {
<div className="row"> <div className="row">
<label>Fee{ updateFeeLevelsButton }</label> <label>Fee{ updateFeeLevelsButton }</label>
<Select <Select
name="fee" name="fee"
className="fee" className="fee"
searchable={ false } searchable={false}
clearable= { false } clearable={false}
value={ selectedFeeLevel } value={selectedFeeLevel}
onChange={ onFeeLevelChange } onChange={onFeeLevelChange}
valueComponent={ FeeSelectValue } valueComponent={FeeSelectValue}
optionComponent={ FeeSelectOption } optionComponent={FeeSelectOption}
disabled={ networkSymbol === currency && data.length > 0 } disabled={networkSymbol === currency && data.length > 0}
optionClassName="fee-option" optionClassName="fee-option"
options={ feeLevels } /> options={feeLevels}
/>
</div> </div>
<AdvancedForm <AdvancedForm
selectedAccount={ props.selectedAccount } selectedAccount={props.selectedAccount}
sendForm={ props.sendForm } sendForm={props.sendForm}
sendFormActions={ props.sendFormActions }> sendFormActions={props.sendFormActions}
<button disabled={ buttonDisabled } onClick={ event => onSend() }>{ buttonLabel }</button> >
<button disabled={buttonDisabled} onClick={event => onSend()}>{ buttonLabel }</button>
</AdvancedForm> </AdvancedForm>
<PendingTransactions <PendingTransactions
pending={ props.selectedAccount.pending } pending={props.selectedAccount.pending}
tokens={ props.selectedAccount.tokens } tokens={props.selectedAccount.tokens}
network={ network } /> network={network}
/>
</section> </section>
); );
} };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import * as React from 'react'; import * as React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -7,9 +7,9 @@ import { connect } from 'react-redux';
import { default as SendFormActions } from '~/js/actions/SendFormActions'; import { default as SendFormActions } from '~/js/actions/SendFormActions';
import * as SessionStorageActions from '~/js/actions/SessionStorageActions'; import * as SessionStorageActions from '~/js/actions/SessionStorageActions';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import SendForm from './SendForm'; import SendForm from './SendForm';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State, Dispatch } from '~/flowtype'; import type { State, Dispatch } from '~/flowtype';
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount'; import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount';
@ -29,23 +29,19 @@ export type DispatchProps = BaseDispatchProps & {
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps; export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
return { className: 'send-from',
className: "send-from", selectedAccount: state.selectedAccount,
selectedAccount: state.selectedAccount, wallet: state.wallet,
wallet: state.wallet,
sendForm: state.sendForm, sendForm: state.sendForm,
fiat: state.fiat, fiat: state.fiat,
localStorage: state.localStorage localStorage: state.localStorage,
}; });
}
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
return { sendFormActions: bindActionCreators(SendFormActions, dispatch),
sendFormActions: bindActionCreators(SendFormActions, dispatch), saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch),
saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch), });
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SendForm) export default connect(mapStateToProps, mapDispatchToProps)(SendForm);

@ -1,33 +1,31 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
const SignVerify = () => { const SignVerify = () => (
return ( <section className="signverify">
<section className="signverify"> <div className="sign">
<div className="sign"> <h2>Sign message</h2>
<h2>Sign message</h2> <label>Message</label>
<label>Message</label> <textarea rows="4" maxLength="255" />
<textarea rows="4" maxLength="255"></textarea> <label>Address</label>
<label>Address</label> <input type="text" />
<input type="text" /> <label>Signature</label>
<label>Signature</label> <textarea rows="4" maxLength="255" readOnly="readonly" />
<textarea rows="4" maxLength="255" readOnly="readonly"></textarea> </div>
</div> <div className="verify">
<div className="verify"> <h2>Verify message</h2>
<h2>Verify message</h2> <label>Message</label>
<label>Message</label> <textarea rows="4" maxLength="255" />
<textarea rows="4" maxLength="255"></textarea> <label>Address</label>
<label>Address</label> <input type="text" />
<input type="text" /> <label>Signature</label>
<label>Signature</label> <textarea rows="4" maxLength="255" />
<textarea rows="4" maxLength="255"></textarea> </div>
</div> </section>
</section> );
);
}
export default connect(null, null)(SignVerify); export default connect(null, null)(SignVerify);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
@ -22,7 +22,7 @@ const Summary = (props: Props) => {
account, account,
network, network,
tokens, tokens,
pending pending,
} = props.selectedAccount; } = props.selectedAccount;
// flow // flow
@ -40,79 +40,77 @@ const Summary = (props: Props) => {
return ( return (
<div> <div>
<h2 className={ `summary-header ${account.network}` }> <h2 className={`summary-header ${account.network}`}>
Account #{ parseInt(account.index) + 1 } Account #{ parseInt(account.index) + 1 }
<a href={ explorerLink } className="gray" target="_blank" rel="noreferrer noopener">See full transaction history</a> <a href={explorerLink} className="gray" target="_blank" rel="noreferrer noopener">See full transaction history</a>
</h2> </h2>
<SummaryDetails <SummaryDetails
coin={ network } coin={network}
summary={ props.summary } summary={props.summary}
balance={ balance } balance={balance}
network={ network.network } network={network.network}
fiat={ props.fiat } fiat={props.fiat}
localStorage={ props.localStorage } localStorage={props.localStorage}
onToggle={ props.onDetailsToggle } /> onToggle={props.onDetailsToggle}
/>
<h2> <h2>
Tokens Tokens
<Tooltip <Tooltip
arrowContent={<div className="rc-tooltip-arrow-inner"></div>} arrowContent={<div className="rc-tooltip-arrow-inner" />}
overlay={ tokensTooltip } overlay={tokensTooltip}
placement="top"> placement="top"
<span className="what-is-it"></span> >
<span className="what-is-it" />
</Tooltip> </Tooltip>
</h2> </h2>
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 Lahod */} {/* 0x58cda554935e4a1f2acbe15f8757400af275e084 Lahod */}
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 T01 */} {/* 0x58cda554935e4a1f2acbe15f8757400af275e084 T01 */}
<div className="filter"> <div className="filter">
<AsyncSelect <AsyncSelect
className="token-select" className="token-select"
multi={ false } multi={false}
autoload={ false } autoload={false}
ignoreCase={ true } ignoreCase
backspaceRemoves={ true } backspaceRemoves
value={ null } value={null}
onChange={ token => props.addToken(token, account) } onChange={token => props.addToken(token, account)}
loadOptions={ input => props.loadTokens(input, account.network) } loadOptions={input => props.loadTokens(input, account.network)}
filterOptions= { filterOptions={
(options: Array<NetworkToken>, search: string, values: Array<NetworkToken>) => { (options: Array<NetworkToken>, search: string, values: Array<NetworkToken>) => options.map((o) => {
return options.map(o => { const added = tokens.find(t => t.symbol === o.symbol);
const added = tokens.find(t => t.symbol === o.symbol); if (added) {
if (added) { return {
return { ...o,
...o, name: `${o.name} (Already added)`,
name: `${o.name} (Already added)`, disabled: true,
disabled: true };
}; }
} else { return o;
return o; })
}
});
}
} }
valueKey="symbol" valueKey="symbol"
labelKey="name" labelKey="name"
placeholder="Search for token" placeholder="Search for token"
searchPromptText="Type token name or address" searchPromptText="Type token name or address"
noResultsText="Token not found" /> noResultsText="Token not found"
/>
</div> </div>
<SummaryTokens <SummaryTokens
pending={ pending } pending={pending}
tokens={ tokens } tokens={tokens}
removeToken={ props.removeToken } /> removeToken={props.removeToken}
/>
</div> </div>
)
}
export default (props: Props) => {
return (
<SelectedAccount { ...props }>
<Summary { ...props} />
</SelectedAccount>
); );
} };
export default (props: Props) => (
<SelectedAccount {...props}>
<Summary {...props} />
</SelectedAccount>
);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
@ -18,12 +18,13 @@ type Props = {
} }
const SummaryDetails = (props: Props): ?React$Element<string> => { const SummaryDetails = (props: Props): ?React$Element<string> => {
if (!props.summary.details) {
if (!props.summary.details) return ( return (
<div className="summary-details"> <div className="summary-details">
<div className="toggle" onClick={ props.onToggle }></div> <div className="toggle" onClick={props.onToggle} />
</div> </div>
); );
}
const selectedCoin = props.coin; const selectedCoin = props.coin;
const fiatRate = props.fiat.find(f => f.network === selectedCoin.network); const fiatRate = props.fiat.find(f => f.network === selectedCoin.network);
@ -32,7 +33,6 @@ const SummaryDetails = (props: Props): ?React$Element<string> => {
let rateColumn = null; let rateColumn = null;
if (fiatRate) { if (fiatRate) {
const accountBalance = new BigNumber(props.balance); const accountBalance = new BigNumber(props.balance);
const fiatValue = new BigNumber(fiatRate.value); const fiatValue = new BigNumber(fiatRate.value);
const fiat = accountBalance.times(fiatValue).toFixed(2); const fiat = accountBalance.times(fiatValue).toFixed(2);
@ -51,7 +51,7 @@ const SummaryDetails = (props: Props): ?React$Element<string> => {
<div className="fiat-value">${ fiatValue.toFixed(2) }</div> <div className="fiat-value">${ fiatValue.toFixed(2) }</div>
<div className="value">1.00 { selectedCoin.symbol }</div> <div className="value">1.00 { selectedCoin.symbol }</div>
</div> </div>
) );
} else { } else {
balanceColumn = ( balanceColumn = (
<div className="column"> <div className="column">
@ -60,16 +60,16 @@ const SummaryDetails = (props: Props): ?React$Element<string> => {
</div> </div>
); );
} }
return ( return (
<div className="summary-details opened"> <div className="summary-details opened">
<div className="toggle" onClick={ props.onToggle }></div> <div className="toggle" onClick={props.onToggle} />
<div className="content"> <div className="content">
{ balanceColumn } { balanceColumn }
{ rateColumn } { rateColumn }
</div> </div>
</div> </div>
); );
} };
export default SummaryDetails; export default SummaryDetails;

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import ColorHash from 'color-hash'; import ColorHash from 'color-hash';
@ -16,40 +16,39 @@ type Props = {
} }
const SummaryTokens = (props: Props) => { const SummaryTokens = (props: Props) => {
if (!props.tokens || props.tokens.length < 1) return null; if (!props.tokens || props.tokens.length < 1) return null;
const bgColor = new ColorHash({lightness: 0.7}); const bgColor = new ColorHash({ lightness: 0.7 });
const textColor = new ColorHash(); const textColor = new ColorHash();
const tokens = props.tokens.map((token, index) => { const tokens = props.tokens.map((token, index) => {
let iconColor = { const iconColor = {
color: textColor.hex(token.name), color: textColor.hex(token.name),
background: bgColor.hex(token.name), background: bgColor.hex(token.name),
borderColor: bgColor.hex(token.name) borderColor: bgColor.hex(token.name),
} };
const pendingAmount: BigNumber = stateUtils.getPendingAmount(props.pending, token.symbol, true); const pendingAmount: BigNumber = stateUtils.getPendingAmount(props.pending, token.symbol, true);
const balance: string = new BigNumber(token.balance).minus(pendingAmount).toString(10); const balance: string = new BigNumber(token.balance).minus(pendingAmount).toString(10);
return ( return (
<div key={ index } className="token"> <div key={index} className="token">
<div className="icon" style={ iconColor }> <div className="icon" style={iconColor}>
<div className="icon-inner"> <div className="icon-inner">
<ScaleText widthOnly><p>{ token.symbol }</p></ScaleText> <ScaleText widthOnly><p>{ token.symbol }</p></ScaleText>
</div> </div>
</div> </div>
<div className="name">{ token.name }</div> <div className="name">{ token.name }</div>
<div className="balance">{ balance } { token.symbol }</div> <div className="balance">{ balance } { token.symbol }</div>
<button className="transparent" onClick={ event => props.removeToken(token) }></button> <button className="transparent" onClick={event => props.removeToken(token)} />
</div> </div>
) );
}); });
return ( return (
<div> <div>
{ tokens } { tokens }
</div> </div>
) );
} };
export default SummaryTokens; export default SummaryTokens;

@ -1,15 +1,15 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import Summary from './Summary'; import Summary from './Summary';
import * as SummaryActions from '~/js/actions/SummaryActions'; import * as SummaryActions from '~/js/actions/SummaryActions';
import * as TokenActions from '~/js/actions/TokenActions'; import * as TokenActions from '~/js/actions/TokenActions';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State, Dispatch } from '~/flowtype'; import type { State, Dispatch } from '~/flowtype';
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount'; import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount';
@ -31,26 +31,22 @@ type DispatchProps = BaseDispatchProps & {
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps; export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
return { className: 'summary',
className: "summary", selectedAccount: state.selectedAccount,
selectedAccount: state.selectedAccount, wallet: state.wallet,
wallet: state.wallet,
tokens: state.tokens,
tokens: state.tokens, summary: state.summary,
summary: state.summary, fiat: state.fiat,
fiat: state.fiat, localStorage: state.localStorage,
localStorage: state.localStorage, });
};
} const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
onDetailsToggle: bindActionCreators(SummaryActions.onDetailsToggle, dispatch),
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { addToken: bindActionCreators(TokenActions.add, dispatch),
return { loadTokens: bindActionCreators(TokenActions.load, dispatch),
onDetailsToggle: bindActionCreators(SummaryActions.onDetailsToggle, dispatch), removeToken: bindActionCreators(TokenActions.remove, dispatch),
addToken: bindActionCreators(TokenActions.add, dispatch), });
loadTokens: bindActionCreators(TokenActions.load, dispatch),
removeToken: bindActionCreators(TokenActions.remove, dispatch), export default connect(mapStateToProps, mapDispatchToProps)(Summary);
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Summary)

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Link, NavLink } from 'react-router-dom'; import { Link, NavLink } from 'react-router-dom';
@ -14,7 +14,6 @@ import type { Props } from './index';
import type { TrezorDevice, Accounts } from '~/flowtype'; import type { TrezorDevice, Accounts } from '~/flowtype';
const AccountSelection = (props: Props): ?React$Element<string> => { const AccountSelection = (props: Props): ?React$Element<string> => {
const selected = props.wallet.selectedDevice; const selected = props.wallet.selectedDevice;
if (!selected) return null; if (!selected) return null;
@ -30,11 +29,11 @@ const AccountSelection = (props: Props): ?React$Element<string> => {
const fiatRate = props.fiat.find(f => f.network === selectedCoin.network); const fiatRate = props.fiat.find(f => f.network === selectedCoin.network);
const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network); const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network);
let selectedAccounts = deviceAccounts.map((account, i) => { let selectedAccounts = deviceAccounts.map((account, i) => {
// const url: string = `${baseUrl}/network/${location.state.network}/account/${i}`; // const url: string = `${baseUrl}/network/${location.state.network}/account/${i}`;
const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/${i}`); const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/${i}`);
let balance: string = 'Loading...'; let balance: string = 'Loading...';
if (account.balance !== '') { if (account.balance !== '') {
const pending = stateUtils.getAccountPendingTx(props.pending, account); const pending = stateUtils.getAccountPendingTx(props.pending, account);
@ -44,35 +43,35 @@ const AccountSelection = (props: Props): ?React$Element<string> => {
if (fiatRate) { if (fiatRate) {
const accountBalance = new BigNumber(availableBalance); const accountBalance = new BigNumber(availableBalance);
const fiat = accountBalance.times(fiatRate.value).toFixed(2); const fiat = accountBalance.times(fiatRate.value).toFixed(2);
balance = `${ availableBalance } ${ selectedCoin.symbol } / $${ fiat }`; balance = `${availableBalance} ${selectedCoin.symbol} / $${fiat}`;
} else { } else {
balance = `${ availableBalance } ${ selectedCoin.symbol }`; balance = `${availableBalance} ${selectedCoin.symbol}`;
} }
} }
return ( return (
<NavLink key={i} activeClassName="selected" className="account" to={ url }> <NavLink key={i} activeClassName="selected" className="account" to={url}>
{ `Account #${(account.index + 1 )}` } { `Account #${(account.index + 1)}` }
<span>{ account.loaded ? balance : "Loading..." }</span> <span>{ account.loaded ? balance : 'Loading...' }</span>
</NavLink> </NavLink>
) );
}); });
if (selectedAccounts.length < 1) { if (selectedAccounts.length < 1) {
if (selected.connected) { if (selected.connected) {
const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/0`); const url: string = location.pathname.replace(/account+\/([0-9]*)/, 'account/0');
selectedAccounts = ( selectedAccounts = (
<NavLink activeClassName="selected" className="account" to={ url }> <NavLink activeClassName="selected" className="account" to={url}>
Account #1 Account #1
<span>Loading...</span> <span>Loading...</span>
</NavLink> </NavLink>
) );
} }
} }
let discoveryStatus = null; let discoveryStatus = null;
const discovery = props.discovery.find(d => d.deviceState === selected.state && d.network === location.state.network); const discovery = props.discovery.find(d => d.deviceState === selected.state && d.network === location.state.network);
if (discovery) { if (discovery) {
if (discovery.completed) { if (discovery.completed) {
// TODO: add only if last one is not empty // TODO: add only if last one is not empty
@ -80,7 +79,7 @@ const AccountSelection = (props: Props): ?React$Element<string> => {
const lastAccount = deviceAccounts[deviceAccounts.length - 1]; const lastAccount = deviceAccounts[deviceAccounts.length - 1];
if (lastAccount && (new BigNumber(lastAccount.balance).greaterThan(0) || lastAccount.nonce > 0)) { if (lastAccount && (new BigNumber(lastAccount.balance).greaterThan(0) || lastAccount.nonce > 0)) {
discoveryStatus = ( discoveryStatus = (
<div className="add-account" onClick={ props.addAccount }> <div className="add-account" onClick={props.addAccount}>
Add account Add account
</div> </div>
); );
@ -89,24 +88,24 @@ const AccountSelection = (props: Props): ?React$Element<string> => {
<div className="aside-tooltip-wrapper"> <div className="aside-tooltip-wrapper">
To add a new account, last account must have some transactions. To add a new account, last account must have some transactions.
</div> </div>
) );
discoveryStatus = ( discoveryStatus = (
<Tooltip <Tooltip
arrowContent={<div className="rc-tooltip-arrow-inner"></div>} arrowContent={<div className="rc-tooltip-arrow-inner" />}
overlay={ tooltip } overlay={tooltip}
placement="top"> placement="top"
<div className="add-account disabled"> >
<div className="add-account disabled">
Add account Add account
</div> </div>
</Tooltip> </Tooltip>
); );
} }
} else if (!selected.connected || !selected.available) { } else if (!selected.connected || !selected.available) {
discoveryStatus = ( discoveryStatus = (
<div className="discovery-status"> <div className="discovery-status">
Accounts could not be loaded Accounts could not be loaded
<span>{ `Connect ${ selected.instanceLabel } device` }</span> <span>{ `Connect ${selected.instanceLabel} device` }</span>
</div> </div>
); );
} else { } else {
@ -118,12 +117,12 @@ const AccountSelection = (props: Props): ?React$Element<string> => {
} }
} }
let backButton = null; let backButton = null;
if (selectedCoin) { if (selectedCoin) {
backButton = ( backButton = (
<NavLink to={ baseUrl } className={ `back ${ selectedCoin.network }` }> <NavLink to={baseUrl} className={`back ${selectedCoin.network}`}>
<span className={ selectedCoin.network }>{ selectedCoin.name }</span> <span className={selectedCoin.network}>{ selectedCoin.name }</span>
</NavLink> </NavLink>
); );
} }
@ -137,6 +136,6 @@ const AccountSelection = (props: Props): ?React$Element<string> => {
{ discoveryStatus } { discoveryStatus }
</section> </section>
); );
} };
export default AccountSelection; export default AccountSelection;

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
//import React, { Node } from 'react'; //import React, { Node } from 'react';
import * as React from 'react'; import * as React from 'react';
@ -20,51 +20,49 @@ type TransitionMenuProps = {
children?: React.Node; children?: React.Node;
} }
const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGroup> => { const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGroup> => (
return ( <TransitionGroup component="div" className="transition-container">
<TransitionGroup component="div" className="transition-container"> <CSSTransition
<CSSTransition key={props.animationType}
key={ props.animationType } onExit={() => { window.dispatchEvent(new Event('resize')); }}
onExit= { () => { window.dispatchEvent( new Event('resize') ) } } onExited={() => window.dispatchEvent(new Event('resize'))}
onExited= { () => window.dispatchEvent( new Event('resize') ) } in
in={ true } out
out={ true } classNames={props.animationType}
classNames={ props.animationType } appear={false}
appear={false} timeout={300}
timeout={ 300 }> >
{ props.children } { props.children }
</CSSTransition> </CSSTransition>
</TransitionGroup> </TransitionGroup>
) );
}
const Aside = (props: Props): React$Element<typeof StickyContainer | string> => { const Aside = (props: Props): React$Element<typeof StickyContainer | string> => {
const selected: ?TrezorDevice = props.wallet.selectedDevice; const selected: ?TrezorDevice = props.wallet.selectedDevice;
const { location } = props.router; const { location } = props.router;
if (location.pathname === '/' || !selected) return (<aside></aside>); if (location.pathname === '/' || !selected) return (<aside />);
let menu = <section />;
let menu = <section></section>;
if (props.deviceDropdownOpened) { if (props.deviceDropdownOpened) {
menu = <DeviceDropdown {...props} />; menu = <DeviceDropdown {...props} />;
} else if (location.state.network) { } else if (location.state.network) {
menu = ( menu = (
<TransitionMenu animationType={ "slide-left" }> <TransitionMenu animationType="slide-left">
<AccountSelection { ...props} /> <AccountSelection {...props} />
</TransitionMenu> </TransitionMenu>
); );
} else if (selected.features && !selected.features.bootloader_mode && selected.features.initialized) { } else if (selected.features && !selected.features.bootloader_mode && selected.features.initialized) {
menu = ( menu = (
<TransitionMenu animationType={ "slide-right" }> <TransitionMenu animationType="slide-right">
<CoinSelection { ...props} /> <CoinSelection {...props} />
</TransitionMenu> </TransitionMenu>
); );
} }
return ( return (
<StickyContainer location={ location.pathname } deviceSelection={ props.deviceDropdownOpened }> <StickyContainer location={location.pathname} deviceSelection={props.deviceDropdownOpened}>
<DeviceSelect {...props} /> <DeviceSelect {...props} />
{ menu } { menu }
<div className="sticky-bottom"> <div className="sticky-bottom">
@ -73,7 +71,7 @@ const Aside = (props: Props): React$Element<typeof StickyContainer | string> =>
</div> </div>
</div> </div>
</StickyContainer> </StickyContainer>
) );
} };
export default Aside; export default Aside;

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { Link, NavLink } from 'react-router-dom'; import { Link, NavLink } from 'react-router-dom';
@ -20,15 +20,15 @@ const CoinSelection = (props: Props): React$Element<string> => {
} }
} }
const walletCoins = config.coins.map(item => { const walletCoins = config.coins.map((item) => {
const url = `${ baseUrl }/network/${ item.network }/account/0`; const url = `${baseUrl}/network/${item.network}/account/0`;
const className = `coin ${ item.network }` const className = `coin ${item.network}`;
return ( return (
<NavLink key={ item.network } to={ url } className={ className }> <NavLink key={item.network} to={url} className={className}>
{ item.name } { item.name }
</NavLink> </NavLink>
) );
}) });
return ( return (
<section> <section>
@ -56,6 +56,6 @@ const CoinSelection = (props: Props): React$Element<string> => {
</a> </a>
</section> </section>
); );
} };
export default CoinSelection; export default CoinSelection;

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Select from 'react-select'; import Select from 'react-select';
@ -9,62 +9,61 @@ import type { Props } from './index';
import type { TrezorDevice } from '~/flowtype'; import type { TrezorDevice } from '~/flowtype';
export const DeviceSelect = (props: Props) => { export const DeviceSelect = (props: Props) => {
const { devices } = props; const { devices } = props;
const { transport } = props.connect; const { transport } = props.connect;
const selected: ?TrezorDevice = props.wallet.selectedDevice; const selected: ?TrezorDevice = props.wallet.selectedDevice;
if (!selected) return null; if (!selected) return null;
let deviceStatus: string = "Connected"; let deviceStatus: string = 'Connected';
let css: string = "device-select device"; let css: string = 'device-select device';
if (props.deviceDropdownOpened) css += " opened"; if (props.deviceDropdownOpened) css += ' opened';
if (!selected.connected) { if (!selected.connected) {
css += " disconnected"; css += ' disconnected';
deviceStatus = "Disconnected"; deviceStatus = 'Disconnected';
} else if (!selected.available) { } else if (!selected.available) {
css += " unavailable"; css += ' unavailable';
deviceStatus = "Unavailable"; deviceStatus = 'Unavailable';
} else { } else {
if (selected.unacquired) { if (selected.unacquired) {
css += " unacquired"; css += ' unacquired';
deviceStatus = "Used in other window"; deviceStatus = 'Used in other window';
} }
if (selected.isUsedElsewhere) { if (selected.isUsedElsewhere) {
css += " used-elsewhere"; css += ' used-elsewhere';
deviceStatus = "Used in other window"; deviceStatus = 'Used in other window';
} else if (selected.featuresNeedsReload) { } else if (selected.featuresNeedsReload) {
css += " reload-features"; css += ' reload-features';
} }
} }
if (selected.features && selected.features.major_version > 1) { if (selected.features && selected.features.major_version > 1) {
css += " trezor-t"; css += ' trezor-t';
} }
const handleOpen = () => { const handleOpen = () => {
props.toggleDeviceDropdown(props.deviceDropdownOpened ? false : true); props.toggleDeviceDropdown(!props.deviceDropdownOpened);
} };
const deviceCount = devices.length; const deviceCount = devices.length;
const webusb: boolean = (transport && transport.version.indexOf('webusb') >= 0) ? true : false; const webusb: boolean = !!((transport && transport.version.indexOf('webusb') >= 0));
const disabled: boolean = (devices.length < 1 && !webusb) || (devices.length === 1 && !selected.features); const disabled: boolean = (devices.length < 1 && !webusb) || (devices.length === 1 && !selected.features);
if (disabled) { if (disabled) {
css += " disabled"; css += ' disabled';
} }
return ( return (
<div className={ css } onClick={ !disabled ? handleOpen : null }> <div className={css} onClick={!disabled ? handleOpen : null}>
<div className="label-container"> <div className="label-container">
<span className="label">{ selected.instanceLabel }</span> <span className="label">{ selected.instanceLabel }</span>
<span className="status">{ deviceStatus }</span> <span className="status">{ deviceStatus }</span>
</div> </div>
{ deviceCount > 1 ? <div className="counter">{ deviceCount }</div> : null } { deviceCount > 1 ? <div className="counter">{ deviceCount }</div> : null }
<div className="arrow"></div> <div className="arrow" />
</div> </div>
); );
} };
type DeviceMenuItem = { type DeviceMenuItem = {
type: string; type: string;
@ -72,8 +71,8 @@ type DeviceMenuItem = {
} }
export class DeviceDropdown extends Component<Props> { export class DeviceDropdown extends Component<Props> {
mouseDownHandler: (event: MouseEvent) => void; mouseDownHandler: (event: MouseEvent) => void;
blurHandler: (event: FocusEvent) => void; blurHandler: (event: FocusEvent) => void;
constructor(props: Props) { constructor(props: Props) {
@ -84,12 +83,11 @@ export class DeviceDropdown extends Component<Props> {
componentDidUpdate() { componentDidUpdate() {
const { transport } = this.props.connect; const { transport } = this.props.connect;
if (transport && transport.version.indexOf('webusb') >= 0) if (transport && transport.version.indexOf('webusb') >= 0) TrezorConnect.renderWebUSBButton();
TrezorConnect.renderWebUSBButton();
} }
mouseDownHandler(event: MouseEvent): void { mouseDownHandler(event: MouseEvent): void {
let elem: any = (event.target : any); let elem: any = (event.target: any);
let block: boolean = false; let block: boolean = false;
while (elem.parentElement) { while (elem.parentElement) {
if (elem.tagName.toLowerCase() === 'aside' || (elem.className && elem.className.indexOf && elem.className.indexOf('modal-container') >= 0)) { if (elem.tagName.toLowerCase() === 'aside' || (elem.className && elem.className.indexOf && elem.className.indexOf('modal-container') >= 0)) {
@ -113,8 +111,7 @@ export class DeviceDropdown extends Component<Props> {
window.addEventListener('mousedown', this.mouseDownHandler, false); window.addEventListener('mousedown', this.mouseDownHandler, false);
// window.addEventListener('blur', this.blurHandler, false); // window.addEventListener('blur', this.blurHandler, false);
const { transport } = this.props.connect; const { transport } = this.props.connect;
if (transport && transport.version.indexOf('webusb') >= 0) if (transport && transport.version.indexOf('webusb') >= 0) TrezorConnect.renderWebUSBButton();
TrezorConnect.renderWebUSBButton();
} }
componentWillUnmount(): void { componentWillUnmount(): void {
@ -136,8 +133,7 @@ export class DeviceDropdown extends Component<Props> {
} }
render() { render() {
const { devices } = this.props;
const { devices } = this.props;
const { transport } = this.props.connect; const { transport } = this.props.connect;
const selected: ?TrezorDevice = this.props.wallet.selectedDevice; const selected: ?TrezorDevice = this.props.wallet.selectedDevice;
if (!selected) return; if (!selected) return;
@ -152,25 +148,23 @@ export class DeviceDropdown extends Component<Props> {
const deviceMenuItems: Array<DeviceMenuItem> = []; const deviceMenuItems: Array<DeviceMenuItem> = [];
if (selected.isUsedElsewhere) { if (selected.isUsedElsewhere) {
deviceMenuItems.push({ type: "reload", label: "Renew session" }); deviceMenuItems.push({ type: 'reload', label: 'Renew session' });
} else if (selected.featuresNeedsReload) { } else if (selected.featuresNeedsReload) {
deviceMenuItems.push({ type: "reload", label: "Renew session" }); deviceMenuItems.push({ type: 'reload', label: 'Renew session' });
} }
deviceMenuItems.push({ type: "settings", label: "Device settings" }); deviceMenuItems.push({ type: 'settings', label: 'Device settings' });
if (selected.features && selected.features.passphrase_protection && selected.connected && selected.available) { if (selected.features && selected.features.passphrase_protection && selected.connected && selected.available) {
deviceMenuItems.push({ type: "clone", label: "Clone device" }); deviceMenuItems.push({ type: 'clone', label: 'Clone device' });
} }
//if (selected.remember) { //if (selected.remember) {
deviceMenuItems.push({ type: "forget", label: "Forget device" }); deviceMenuItems.push({ type: 'forget', label: 'Forget device' });
//} //}
const deviceMenuButtons = deviceMenuItems.map((item, index) => { const deviceMenuButtons = deviceMenuItems.map((item, index) => (
return ( <div key={item.type} className={item.type} onClick={event => this.onDeviceMenuClick(item, selected)}>{ item.label}</div>
<div key={ item.type } className={ item.type } onClick={ (event) => this.onDeviceMenuClick(item, selected) }>{ item.label}</div> ));
)
});
currentDeviceMenu = deviceMenuButtons.length < 1 ? null : ( currentDeviceMenu = deviceMenuButtons.length < 1 ? null : (
<div className="device-menu"> <div className="device-menu">
{ deviceMenuButtons } { deviceMenuButtons }
@ -181,34 +175,37 @@ export class DeviceDropdown extends Component<Props> {
const deviceList = devices.map((dev, index) => { const deviceList = devices.map((dev, index) => {
if (dev === selected) return null; if (dev === selected) return null;
let deviceStatus: string = "Connected"; let deviceStatus: string = 'Connected';
let css: string = "device item" let css: string = 'device item';
if (dev.unacquired || dev.isUsedElsewhere) { if (dev.unacquired || dev.isUsedElsewhere) {
deviceStatus = "Used in other window"; deviceStatus = 'Used in other window';
css += " unacquired"; css += ' unacquired';
} else if (!dev.connected) { } else if (!dev.connected) {
deviceStatus = "Disconnected"; deviceStatus = 'Disconnected';
css += " disconnected"; css += ' disconnected';
} else if (!dev.available) { } else if (!dev.available) {
deviceStatus = "Unavailable"; deviceStatus = 'Unavailable';
css += " unavailable"; css += ' unavailable';
} }
if (dev.features && dev.features.major_version > 1) { if (dev.features && dev.features.major_version > 1) {
css += " trezor-t"; css += ' trezor-t';
} }
return ( return (
<div key={index} className={ css } onClick={ () => this.props.onSelectDevice(dev) } > <div key={index} className={css} onClick={() => this.props.onSelectDevice(dev)}>
<div className="label-container"> <div className="label-container">
<span className="label">{ dev.instanceLabel }</span> <span className="label">{ dev.instanceLabel }</span>
<span className="status">{ deviceStatus }</span> <span className="status">{ deviceStatus }</span>
</div> </div>
<div className="forget-button" onClick={ (event) => { <div
event.stopPropagation(); className="forget-button"
event.preventDefault(); onClick={(event) => {
this.onDeviceMenuClick({ type: 'forget', label: ''}, dev); event.stopPropagation();
} }> </div> event.preventDefault();
this.onDeviceMenuClick({ type: 'forget', label: '' }, dev);
}}
/>
</div> </div>
); );
}); });

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
// https://github.com/KyleAMathews/react-headroom/blob/master/src/shouldUpdate.js // https://github.com/KyleAMathews/react-headroom/blob/master/src/shouldUpdate.js
@ -14,18 +14,25 @@ type Props = {
} }
export default class StickyContainer extends React.PureComponent<Props> { export default class StickyContainer extends React.PureComponent<Props> {
// Class variables. // Class variables.
currentScrollY: number = 0; currentScrollY: number = 0;
lastKnownScrollY: number = 0; lastKnownScrollY: number = 0;
topOffset: number = 0; topOffset: number = 0;
firstRender: boolean = false; firstRender: boolean = false;
framePending: boolean = false; framePending: boolean = false;
stickToBottom: boolean = false; stickToBottom: boolean = false;
top: number = 0; top: number = 0;
aside: ?HTMLElement; aside: ?HTMLElement;
wrapper: ?HTMLElement; wrapper: ?HTMLElement;
subscribers = []; subscribers = [];
handleResize = (event: Event) => { handleResize = (event: Event) => {
@ -45,7 +52,7 @@ export default class StickyContainer extends React.PureComponent<Props> {
if (!wrapper || !aside) return; if (!wrapper || !aside) return;
const bottom: ?HTMLElement = wrapper.querySelector('.sticky-bottom'); const bottom: ?HTMLElement = wrapper.querySelector('.sticky-bottom');
if (!bottom) return; if (!bottom) return;
const viewportHeight: number = getViewportHeight(); const viewportHeight: number = getViewportHeight();
const bottomBounds = bottom.getBoundingClientRect(); const bottomBounds = bottom.getBoundingClientRect();
const asideBounds = aside.getBoundingClientRect(); const asideBounds = aside.getBoundingClientRect();
@ -55,12 +62,12 @@ export default class StickyContainer extends React.PureComponent<Props> {
if (asideBounds.top < 0) { if (asideBounds.top < 0) {
wrapper.classList.add('fixed'); wrapper.classList.add('fixed');
let maxTop : number= 1; let maxTop: number = 1;
if (wrapperBounds.height > viewportHeight) { if (wrapperBounds.height > viewportHeight) {
const bottomOutOfBounds: boolean = (bottomBounds.bottom <= viewportHeight && scrollDirection === 'down'); const bottomOutOfBounds: boolean = (bottomBounds.bottom <= viewportHeight && scrollDirection === 'down');
const topOutOfBounds: boolean = (wrapperBounds.top > 0 && scrollDirection === 'up'); const topOutOfBounds: boolean = (wrapperBounds.top > 0 && scrollDirection === 'up');
if (!bottomOutOfBounds && !topOutOfBounds) { if (!bottomOutOfBounds && !topOutOfBounds) {
this.topOffset += scrollDirection === 'down' ? - distanceScrolled : distanceScrolled; this.topOffset += scrollDirection === 'down' ? -distanceScrolled : distanceScrolled;
} }
maxTop = viewportHeight - wrapperBounds.height; maxTop = viewportHeight - wrapperBounds.height;
} }
@ -68,26 +75,23 @@ export default class StickyContainer extends React.PureComponent<Props> {
if (this.topOffset > 0) this.topOffset = 0; if (this.topOffset > 0) this.topOffset = 0;
if (maxTop < 0 && this.topOffset < maxTop) this.topOffset = maxTop; if (maxTop < 0 && this.topOffset < maxTop) this.topOffset = maxTop;
wrapper.style.top = `${this.topOffset}px`; wrapper.style.top = `${this.topOffset}px`;
} else { } else {
wrapper.classList.remove('fixed'); wrapper.classList.remove('fixed');
wrapper.style.top = `0px`; wrapper.style.top = '0px';
this.topOffset = 0; this.topOffset = 0;
} }
if (wrapperBounds.height > viewportHeight) { if (wrapperBounds.height > viewportHeight) {
wrapper.classList.remove('fixed-bottom'); wrapper.classList.remove('fixed-bottom');
} else { } else if (wrapper.classList.contains('fixed-bottom')) {
if (wrapper.classList.contains('fixed-bottom')) { if (bottomBounds.top < wrapperBounds.bottom - bottomBounds.height) {
if (bottomBounds.top < wrapperBounds.bottom - bottomBounds.height) { wrapper.classList.remove('fixed-bottom');
wrapper.classList.remove('fixed-bottom');
}
} else if (bottomBounds.bottom < viewportHeight) {
wrapper.classList.add('fixed-bottom');
} }
} else if (bottomBounds.bottom < viewportHeight) {
wrapper.classList.add('fixed-bottom');
} }
aside.style.minHeight = `${ wrapperBounds.height }px`; aside.style.minHeight = `${wrapperBounds.height}px`;
} }
update = () => { update = () => {
@ -102,7 +106,7 @@ export default class StickyContainer extends React.PureComponent<Props> {
window.addEventListener('resize', this.handleScroll); window.addEventListener('resize', this.handleScroll);
raf(this.update); raf(this.update);
} }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleScroll); window.removeEventListener('resize', this.handleScroll);
@ -126,19 +130,20 @@ export default class StickyContainer extends React.PureComponent<Props> {
render() { render() {
return ( return (
<aside <aside
ref={ node => this.aside = node } ref={node => this.aside = node}
onScroll={this.handleScroll} onScroll={this.handleScroll}
onTouchStart={this.handleScroll} onTouchStart={this.handleScroll}
onTouchMove={this.handleScroll} onTouchMove={this.handleScroll}
onTouchEnd={this.handleScroll} onTouchEnd={this.handleScroll}
> >
<div <div
className="sticky-container" className="sticky-container"
ref={ node => this.wrapper = node }> ref={node => this.wrapper = node}
{ this.props.children } >
</div> { this.props.children }
</aside> </div>
</aside>
); );
} }
} }

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -15,7 +15,7 @@ import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State, Dispatch } from '~/flowtype'; import type { State, Dispatch } from '~/flowtype';
type OwnProps = { type OwnProps = {
} }
type StateProps = { type StateProps = {
@ -43,35 +43,31 @@ type DispatchProps = {
export type Props = StateProps & DispatchProps; export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
return { connect: state.connect,
connect: state.connect, accounts: state.accounts,
accounts: state.accounts, router: state.router,
router: state.router, deviceDropdownOpened: state.wallet.dropdownOpened,
deviceDropdownOpened: state.wallet.dropdownOpened, fiat: state.fiat,
fiat: state.fiat, localStorage: state.localStorage,
localStorage: state.localStorage, discovery: state.discovery,
discovery: state.discovery, wallet: state.wallet,
wallet: state.wallet, devices: state.devices,
devices: state.devices, pending: state.pending,
pending: state.pending, });
};
}
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
return { //onAccountSelect: bindActionCreators(AccountActions.onAccountSelect, dispatch),
//onAccountSelect: bindActionCreators(AccountActions.onAccountSelect, dispatch), toggleDeviceDropdown: bindActionCreators(toggleDeviceDropdown, dispatch),
toggleDeviceDropdown: bindActionCreators(toggleDeviceDropdown, dispatch), addAccount: bindActionCreators(TrezorConnectActions.addAccount, dispatch),
addAccount: bindActionCreators(TrezorConnectActions.addAccount, dispatch), acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch), forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch),
forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch), duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch),
duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch), gotoDeviceSettings: bindActionCreators(TrezorConnectActions.gotoDeviceSettings, dispatch),
gotoDeviceSettings: bindActionCreators(TrezorConnectActions.gotoDeviceSettings, dispatch), onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch),
onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch), });
};
}
// export default connect(mapStateToProps, mapDispatchToProps)(Aside); // export default connect(mapStateToProps, mapDispatchToProps)(Aside);
export default withRouter( export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Aside) connect(mapStateToProps, mapDispatchToProps)(Aside),
); );

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import * as React from 'react'; import * as React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -28,43 +28,37 @@ type ContentProps = {
} }
const Content = (props: ContentProps) => { const Content = (props: ContentProps) => (
return ( <article>
<article> <nav>
<nav> <Route path="/device/:device/network/:network/account/:account" component={AccountTabs} />
<Route path="/device/:device/network/:network/account/:account" component={ AccountTabs } /> <Route path="/device/:device/device-settings" component={DeviceSettingsTabs} />
<Route path="/device/:device/device-settings" component={ DeviceSettingsTabs } /> </nav>
</nav> <Notifications />
<Notifications /> <Log />
<Log /> { props.children }
{ props.children } <Footer />
<Footer /> </article>
</article> );
);
}
const Wallet = (props: WalletContainerProps) => { const Wallet = (props: WalletContainerProps) => (
return ( <div className="app">
<div className="app"> <Header />
<Header /> {/* <div>{ props.wallet.online ? "ONLINE" : "OFFLINE" }</div> */}
{/* <div>{ props.wallet.online ? "ONLINE" : "OFFLINE" }</div> */} <main>
<main> <AsideContainer />
<AsideContainer /> <Content>
<Content> { props.children }
{ props.children } </Content>
</Content> </main>
</main> <ModalContainer />
<ModalContainer /> </div>
</div> );
);
}
const mapStateToProps: MapStateToProps<State, {}, WalletContainerProps> = (state: State, own: {}): WalletContainerProps => { const mapStateToProps: MapStateToProps<State, {}, WalletContainerProps> = (state: State, own: {}): WalletContainerProps => ({
return { wallet: state.wallet,
wallet: state.wallet });
};
}
export default withRouter( export default withRouter(
connect(mapStateToProps, null)(Wallet) connect(mapStateToProps, null)(Wallet),
); );

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -8,44 +8,40 @@ import { Notification } from '~/js/components/common/Notification';
import * as TrezorConnectActions from '~/js/actions/TrezorConnectActions'; import * as TrezorConnectActions from '~/js/actions/TrezorConnectActions';
import type { State, Dispatch } from '~/flowtype'; import type { State, Dispatch } from '~/flowtype';
type Props = { type Props = {
acquiring: boolean; acquiring: boolean;
acquireDevice: typeof TrezorConnectActions.acquire acquireDevice: typeof TrezorConnectActions.acquire
} }
const Acquire = (props: Props) => { const Acquire = (props: Props) => {
const actions = props.acquiring ? [] : [ const actions = props.acquiring ? [] : [
{ {
label: 'Acquire device', label: 'Acquire device',
callback: () => { callback: () => {
props.acquireDevice() props.acquireDevice();
} },
} },
]; ];
return ( return (
<section className="acquire"> <section className="acquire">
<Notification <Notification
title="Device is used in other window" title="Device is used in other window"
message="Do you want to use your device in this window?" message="Do you want to use your device in this window?"
className="info" className="info"
cancelable={ false } cancelable={false}
actions={ actions } actions={actions}
/> />
</section> </section>
); );
} };
export default connect( export default connect(
(state: State) => { (state: State) => ({
return { acquiring: state.connect.acquiring,
acquiring: state.connect.acquiring }),
}; (dispatch: Dispatch) => ({
}, acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
(dispatch: Dispatch) => { }),
return {
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
};
}
)(Acquire); )(Acquire);

@ -1,19 +1,17 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
const Bootloader = () => { const Bootloader = () => (
return ( <section className="device-settings">
<section className="device-settings"> <div className="row">
<div className="row"> <h2>Your device is in firmware update mode</h2>
<h2>Your device is in firmware update mode</h2> <p>Please re-connect it</p>
<p>Please re-connect it</p> </div>
</div> </section>
</section> );
);
}
export default connect(null, null)(Bootloader); export default connect(null, null)(Bootloader);

@ -1,22 +1,20 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import DashboardImg from '~/images/dashboard.png'; import DashboardImg from '~/images/dashboard.png';
const Dashboard = () => { const Dashboard = () => (
return ( <section className="dashboard">
<section className="dashboard"> <h2>Dashboard</h2>
<h2>Dashboard</h2> <div className="row">
<div className="row"> <h2>Please select your coin</h2>
<h2>Please select your coin</h2> <p>You will gain access to recieving &amp; sending selected coin</p>
<p>You will gain access to recieving &amp; sending selected coin</p> <img src={DashboardImg} height="34" width="auto" alt="Dashboard" />
<img src={ DashboardImg } height="34" width="auto" alt="Dashboard" /> </div>
</div> </section>
</section> );
);
}
export default connect(null, null)(Dashboard); export default connect(null, null)(Dashboard);

@ -1,20 +1,18 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
export const DeviceSettings = () => { export const DeviceSettings = () => (
return ( <section className="device-settings">
<section className="device-settings"> <div className="row">
<div className="row"> <h2>Device settings is under construction</h2>
<h2>Device settings is under construction</h2> <p>Please use Bitcoin wallet interface to change your device settings</p>
<p>Please use Bitcoin wallet interface to change your device settings</p> <a className="button" href="https://wallet.trezor.io/">Take me to the Bitcoin wallet</a>
<a className="button" href="https://wallet.trezor.io/">Take me to the Bitcoin wallet</a> </div>
</div> </section>
</section> );
);
}
export default connect(null, null)(DeviceSettings); export default connect(null, null)(DeviceSettings);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
@ -16,7 +16,6 @@ type State = {
} }
const AccountTabs = (props: any): any => { const AccountTabs = (props: any): any => {
const urlParams = props.match.params; const urlParams = props.match.params;
const basePath = `/device/${urlParams.device}/network/${urlParams.network}/account/${urlParams.account}`; const basePath = `/device/${urlParams.device}/network/${urlParams.network}/account/${urlParams.account}`;
@ -25,6 +24,6 @@ const AccountTabs = (props: any): any => {
<a>Device settings</a> <a>Device settings</a>
</div> </div>
); );
} };
export default AccountTabs; export default AccountTabs;

@ -1,20 +1,18 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
const Initialize = () => { const Initialize = () => (
return ( <section className="device-settings">
<section className="device-settings"> <div className="row">
<div className="row"> <h2>Your device is in not initialized</h2>
<h2>Your device is in not initialized</h2> <p>Please use Bitcoin wallet interface to start initialization process</p>
<p>Please use Bitcoin wallet interface to start initialization process</p> <a className="button" href="https://wallet.trezor.io/">Take me to the Bitcoin wallet</a>
<a className="button" href="https://wallet.trezor.io/">Take me to the Bitcoin wallet</a> </div>
</div> </section>
</section> );
);
}
export default connect(null, null)(Initialize); export default connect(null, null)(Initialize);

@ -1,16 +1,14 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
export const WalletSettings = () => { export const WalletSettings = () => (
return ( <section className="settings">
<section className="settings">
Wallet settings Wallet settings
</section> </section>
); );
}
export default connect(null, null)(WalletSettings); export default connect(null, null)(WalletSettings);

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
@ -14,8 +14,8 @@ if (root) {
} }
window.onbeforeunload = () => { window.onbeforeunload = () => {
store.dispatch( onBeforeUnload() ); store.dispatch(onBeforeUnload());
} };
if (typeof module !== undefined && module.hasOwnProperty('hot')) { if (typeof module !== undefined && module.hasOwnProperty('hot')) {
// $FlowIssue // $FlowIssue

@ -1,15 +1,15 @@
/* @flow */ /* @flow */
'use strict';
import * as CONNECT from '../actions/constants/TrezorConnect'; import * as CONNECT from '../actions/constants/TrezorConnect';
import * as WALLET from '../actions/constants/wallet'; import * as WALLET from '../actions/constants/wallet';
import * as ACCOUNT from '../actions/constants/account'; import * as ACCOUNT from '../actions/constants/account';
import type { Action, TrezorDevice } from '~/flowtype'; import type { Action, TrezorDevice } from '~/flowtype';
import type { import type {
AccountCreateAction, AccountCreateAction,
AccountSetBalanceAction, AccountSetBalanceAction,
AccountSetNonceAction AccountSetNonceAction,
} from '../actions/AccountsActions'; } from '../actions/AccountsActions';
export type Account = { export type Account = {
@ -28,20 +28,16 @@ export type State = Array<Account>;
const initialState: State = []; const initialState: State = [];
export const findAccount = (state: State, index: number, deviceState: string, network: string): ?Account => { export const findAccount = (state: State, index: number, deviceState: string, network: string): ?Account => state.find(a => a.deviceState === deviceState && a.index === index && a.network === network);
return state.find(a => a.deviceState === deviceState && a.index === index && a.network === network);
}
export const findDeviceAccounts = (state: State, device: TrezorDevice, network: string): Array<Account> => { export const findDeviceAccounts = (state: State, device: TrezorDevice, network: string): Array<Account> => {
if (network) { if (network) {
return state.filter((addr) => addr.deviceState === device.state && addr.network === network); return state.filter(addr => addr.deviceState === device.state && addr.network === network);
} else {
return state.filter((addr) => addr.deviceState === device.state);
} }
} return state.filter(addr => addr.deviceState === device.state);
};
const createAccount = (state: State, action: AccountCreateAction): State => { const createAccount = (state: State, action: AccountCreateAction): State => {
// TODO check with device_id // TODO check with device_id
// check if account was created before // check if account was created before
// const exist: ?Account = state.find(account => account.address === action.address && account.network === action.network && action.device.features && account.deviceID === action.device.features.device_id); // const exist: ?Account = state.find(account => account.address === action.address && account.network === action.network && action.device.features && account.deviceID === action.device.features.device_id);
@ -60,69 +56,64 @@ const createAccount = (state: State, action: AccountCreateAction): State => {
address: action.address, address: action.address,
balance: '0', balance: '0',
nonce: 0, nonce: 0,
} };
const newState: State = [ ...state ]; const newState: State = [...state];
newState.push(account); newState.push(account);
return newState; return newState;
} };
const removeAccounts = (state: State, device: TrezorDevice): State => state.filter(account => account.deviceState !== device.state);
const removeAccounts = (state: State, device: TrezorDevice): State => {
//return state.filter(account => device.features && account.deviceID !== device.features.device_id);
return state.filter(account => account.deviceState !== device.state);
}
const clear = (state: State, devices: Array<TrezorDevice>): State => { const clear = (state: State, devices: Array<TrezorDevice>): State => {
let newState: State = [ ...state ]; let newState: State = [...state];
devices.forEach(d => { devices.forEach((d) => {
newState = removeAccounts(newState, d); newState = removeAccounts(newState, d);
}); });
return newState; return newState;
} };
const setBalance = (state: State, action: AccountSetBalanceAction): State => { const setBalance = (state: State, action: AccountSetBalanceAction): State => {
const index: number = state.findIndex(account => account.address === action.address && account.network === action.network && account.deviceState === action.deviceState); const index: number = state.findIndex(account => account.address === action.address && account.network === action.network && account.deviceState === action.deviceState);
const newState: State = [ ...state ]; const newState: State = [...state];
newState[index].loaded = true; newState[index].loaded = true;
newState[index].balance = action.balance; newState[index].balance = action.balance;
return newState; return newState;
} };
const setNonce = (state: State, action: AccountSetNonceAction): State => { const setNonce = (state: State, action: AccountSetNonceAction): State => {
const index: number = state.findIndex(account => account.address === action.address && account.network === action.network && account.deviceState === action.deviceState); const index: number = state.findIndex(account => account.address === action.address && account.network === action.network && account.deviceState === action.deviceState);
const newState: State = [ ...state ]; const newState: State = [...state];
newState[index].loaded = true; newState[index].loaded = true;
newState[index].nonce = action.nonce; newState[index].nonce = action.nonce;
return newState; return newState;
} };
export default (state: State = initialState, action: Action): State => { export default (state: State = initialState, action: Action): State => {
switch (action.type) { switch (action.type) {
case ACCOUNT.CREATE:
case ACCOUNT.CREATE :
return createAccount(state, action); return createAccount(state, action);
case CONNECT.FORGET : case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE : case CONNECT.FORGET_SINGLE:
return removeAccounts(state, action.device); return removeAccounts(state, action.device);
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA : case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:
return clear(state, action.devices); return clear(state, action.devices);
//case CONNECT.FORGET_SINGLE : //case CONNECT.FORGET_SINGLE :
// return forgetAccounts(state, action); // return forgetAccounts(state, action);
case ACCOUNT.SET_BALANCE : case ACCOUNT.SET_BALANCE:
return setBalance(state, action); return setBalance(state, action);
case ACCOUNT.SET_NONCE : case ACCOUNT.SET_NONCE:
return setNonce(state, action); return setNonce(state, action);
case ACCOUNT.FROM_STORAGE : case ACCOUNT.FROM_STORAGE:
return action.payload; return action.payload;
default: default:
return state; return state;
} }
};
}

@ -1,19 +1,18 @@
/* @flow */ /* @flow */
'use strict';
import { TRANSPORT, DEVICE } from 'trezor-connect'; import { TRANSPORT, DEVICE } from 'trezor-connect';
import type { Device } from 'trezor-connect';
import * as CONNECT from '../actions/constants/TrezorConnect'; import * as CONNECT from '../actions/constants/TrezorConnect';
import * as WALLET from '../actions/constants/wallet'; import * as WALLET from '../actions/constants/wallet';
import type { Action, TrezorDevice } from '~/flowtype'; import type { Action, TrezorDevice } from '~/flowtype';
import type { Device } from 'trezor-connect';
export type State = Array<TrezorDevice>; export type State = Array<TrezorDevice>;
const initialState: State = []; const initialState: State = [];
const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): TrezorDevice => { const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): TrezorDevice => {
// do not merge if passphrase protection was changed // do not merge if passphrase protection was changed
// if (upcoming.features && current.features) { // if (upcoming.features && current.features) {
// if (upcoming.features.passphrase_protection !== current.features.passphrase_protection) { // if (upcoming.features.passphrase_protection !== current.features.passphrase_protection) {
@ -24,7 +23,7 @@ const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): T
let instanceLabel = current.instanceLabel; let instanceLabel = current.instanceLabel;
if (upcoming.label !== current.label) { if (upcoming.label !== current.label) {
instanceLabel = upcoming.label instanceLabel = upcoming.label;
if (typeof current.instance === 'number') { if (typeof current.instance === 'number') {
instanceLabel += ` (${current.instanceName || current.instance})`; instanceLabel += ` (${current.instanceName || current.instance})`;
} }
@ -42,7 +41,7 @@ const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): T
instanceName: typeof upcoming.instanceName === 'string' ? upcoming.instanceName : current.instanceName, instanceName: typeof upcoming.instanceName === 'string' ? upcoming.instanceName : current.instanceName,
state: current.state, state: current.state,
ts: typeof upcoming.ts === 'number' ? upcoming.ts : current.ts, ts: typeof upcoming.ts === 'number' ? upcoming.ts : current.ts,
} };
// corner-case: trying to merge unacquired device with acquired // corner-case: trying to merge unacquired device with acquired
// make sure that sensitive fields will not be changed and device will remain acquired // make sure that sensitive fields will not be changed and device will remain acquired
if (upcoming.unacquired && current.state) { if (upcoming.unacquired && current.state) {
@ -52,10 +51,9 @@ const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): T
} }
return dev; return dev;
} };
const addDevice = (state: State, device: Device): State => { const addDevice = (state: State, device: Device): State => {
let affectedDevices: Array<TrezorDevice> = []; let affectedDevices: Array<TrezorDevice> = [];
let otherDevices: Array<TrezorDevice> = []; let otherDevices: Array<TrezorDevice> = [];
if (!device.features) { if (!device.features) {
@ -84,9 +82,9 @@ const addDevice = (state: State, device: Device): State => {
instanceLabel: device.label, instanceLabel: device.label,
instanceName: null, instanceName: null,
ts: new Date().getTime(), ts: new Date().getTime(),
} };
if (affectedDevices.length > 0 ) { if (affectedDevices.length > 0) {
// check if freshly added device has different "passphrase_protection" settings // check if freshly added device has different "passphrase_protection" settings
// let hasDifferentPassphraseSettings: boolean = false; // let hasDifferentPassphraseSettings: boolean = false;
@ -114,26 +112,21 @@ const addDevice = (state: State, device: Device): State => {
// changedDevices.push(newDevice); // changedDevices.push(newDevice);
// } // }
const changedDevices: Array<TrezorDevice> = affectedDevices.filter(d => d.features && d.features.passphrase_protection === device.features.passphrase_protection).map(d => { const changedDevices: Array<TrezorDevice> = affectedDevices.filter(d => d.features && d.features.passphrase_protection === device.features.passphrase_protection).map(d => mergeDevices(d, { ...device, connected: true, available: true }));
return mergeDevices(d, { ...device, connected: true, available: true } );
});
if (changedDevices.length !== affectedDevices.length) { if (changedDevices.length !== affectedDevices.length) {
changedDevices.push(newDevice); changedDevices.push(newDevice);
} }
return otherDevices.concat(changedDevices); return otherDevices.concat(changedDevices);
} else {
return otherDevices.concat([newDevice]);
} }
} return otherDevices.concat([newDevice]);
};
const duplicate = (state: State, device: TrezorDevice): State => { const duplicate = (state: State, device: TrezorDevice): State => {
if (!device.features) return state; if (!device.features) return state;
const newState: State = [ ...state ]; const newState: State = [...state];
const instance: number = getNewInstance(state, device); const instance: number = getNewInstance(state, device);
@ -143,25 +136,22 @@ const duplicate = (state: State, device: TrezorDevice): State => {
remember: false, remember: false,
state: null, state: null,
// instance, (instance is already part of device - added in modal) // instance, (instance is already part of device - added in modal)
instanceLabel: `${device.label} (${ device.instanceName || instance })`, instanceLabel: `${device.label} (${device.instanceName || instance})`,
ts: new Date().getTime(), ts: new Date().getTime(),
} };
newState.push(newDevice); newState.push(newDevice);
return newState; return newState;
} };
const changeDevice = (state: State, device: Device): State => { const changeDevice = (state: State, device: Device): State => {
// change only acquired devices // change only acquired devices
if (!device.features) return state; if (!device.features) return state;
// find devices with the same device_id and passphrase_protection settings // find devices with the same device_id and passphrase_protection settings
// or devices with the same path (TODO: should be that way?) // or devices with the same path (TODO: should be that way?)
const affectedDevices: Array<TrezorDevice> = state.filter(d => const affectedDevices: Array<TrezorDevice> = state.filter(d => (d.features && d.features.device_id === device.features.device_id && d.features.passphrase_protection === device.features.passphrase_protection)
(d.features && d.features.device_id === device.features.device_id && d.features.passphrase_protection === device.features.passphrase_protection) || || (d.features && d.path.length > 0 && d.path === device.path));
(d.features && d.path.length > 0 && d.path === device.path)
);
const otherDevices: Array<TrezorDevice> = state.filter(d => affectedDevices.indexOf(d) === -1); const otherDevices: Array<TrezorDevice> = state.filter(d => affectedDevices.indexOf(d) === -1);
@ -172,7 +162,7 @@ const changeDevice = (state: State, device: Device): State => {
} }
return state; return state;
} };
const authDevice = (state: State, device: TrezorDevice, deviceState: string): State => { const authDevice = (state: State, device: TrezorDevice, deviceState: string): State => {
const affectedDevice: ?TrezorDevice = state.find(d => d.path === device.path && d.instance === device.instance); const affectedDevice: ?TrezorDevice = state.find(d => d.path === device.path && d.instance === device.instance);
@ -183,47 +173,39 @@ const authDevice = (state: State, device: TrezorDevice, deviceState: string): St
return otherDevices.concat([affectedDevice]); return otherDevices.concat([affectedDevice]);
} }
return state; return state;
} };
// Transform JSON form local storage into State // Transform JSON form local storage into State
const devicesFromStorage = (devices: Array<TrezorDevice>): State => { const devicesFromStorage = (devices: Array<TrezorDevice>): State => devices.map((d: TrezorDevice) => ({
return devices.map( (d: TrezorDevice) => { ...d,
return { connected: false,
...d, available: false,
connected: false, path: '',
available: false, acquiring: false,
path: '', featuresNeedsReload: false,
acquiring: false, isUsedElsewhere: false,
featuresNeedsReload: false, }));
isUsedElsewhere: false
}
});
}
// Remove all device reference from State // Remove all device reference from State
const forgetDevice = (state: State, device: TrezorDevice): State => { const forgetDevice = (state: State, device: TrezorDevice): State => state.filter(d => d.remember || (d.features && device.features && d.features.device_id !== device.features.device_id) || (!d.features && d.path !== device.path));
// remove all instances after disconnect (remember request declined)
// TODO: filter clones
return state.filter(d => d.remember || (d.features && device.features && d.features.device_id !== device.features.device_id) || (!d.features && d.path !== device.path));
}
// Remove single device reference from State // Remove single device reference from State
const forgetSingleDevice = (state: State, device: TrezorDevice): State => { const forgetSingleDevice = (state: State, device: TrezorDevice): State => {
// remove only one instance (called from Aside button) // remove only one instance (called from Aside button)
const newState: State = [ ...state ]; const newState: State = [...state];
newState.splice(newState.indexOf(device), 1); newState.splice(newState.indexOf(device), 1);
return newState; return newState;
} };
const disconnectDevice = (state: State, device: Device): State => { const disconnectDevice = (state: State, device: Device): State => {
const affectedDevices: State = state.filter(d => d.path === device.path || (d.features && device.features && d.features.device_id === device.features.device_id)); const affectedDevices: State = state.filter(d => d.path === device.path || (d.features && device.features && d.features.device_id === device.features.device_id));
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1); const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
if (affectedDevices.length > 0) { if (affectedDevices.length > 0) {
const acquiredDevices = affectedDevices.filter(d => !d.unacquired && d.state); const acquiredDevices = affectedDevices.filter(d => !d.unacquired && d.state);
return otherDevices.concat( acquiredDevices.map(d => { return otherDevices.concat(acquiredDevices.map((d) => {
d.connected = false; d.connected = false;
d.available = false; d.available = false;
d.isUsedElsewhere = false; d.isUsedElsewhere = false;
@ -234,74 +216,67 @@ const disconnectDevice = (state: State, device: Device): State => {
} }
return state; return state;
} };
const onSelectedDevice = (state: State, device: ?TrezorDevice): State => { const onSelectedDevice = (state: State, device: ?TrezorDevice): State => {
if (device) { if (device) {
const otherDevices: Array<TrezorDevice> = state.filter(d => d !== device); const otherDevices: Array<TrezorDevice> = state.filter(d => d !== device);
return otherDevices.concat([ { ...device, ts: new Date().getTime() } ]); return otherDevices.concat([{ ...device, ts: new Date().getTime() }]);
} }
return state; return state;
} };
export default function devices(state: State = initialState, action: Action): State { export default function devices(state: State = initialState, action: Action): State {
switch (action.type) { switch (action.type) {
case CONNECT.DEVICE_FROM_STORAGE:
case CONNECT.DEVICE_FROM_STORAGE :
return devicesFromStorage(action.payload); return devicesFromStorage(action.payload);
case CONNECT.DUPLICATE : case CONNECT.DUPLICATE:
return duplicate(state, action.device); return duplicate(state, action.device);
case CONNECT.AUTH_DEVICE : case CONNECT.AUTH_DEVICE:
return authDevice(state, action.device, action.state); return authDevice(state, action.device, action.state);
case CONNECT.REMEMBER : case CONNECT.REMEMBER:
return changeDevice(state, { ...action.device, path: '', remember: true } ); return changeDevice(state, { ...action.device, path: '', remember: true });
case CONNECT.FORGET : case CONNECT.FORGET:
return forgetDevice(state, action.device); return forgetDevice(state, action.device);
case CONNECT.FORGET_SINGLE : case CONNECT.FORGET_SINGLE:
return forgetSingleDevice(state, action.device); return forgetSingleDevice(state, action.device);
case DEVICE.CONNECT : case DEVICE.CONNECT:
case DEVICE.CONNECT_UNACQUIRED : case DEVICE.CONNECT_UNACQUIRED:
return addDevice(state, action.device); return addDevice(state, action.device);
case DEVICE.CHANGED : case DEVICE.CHANGED:
return changeDevice(state, { ...action.device, connected: true, available: true }); return changeDevice(state, { ...action.device, connected: true, available: true });
// TODO: check if available will propagate to unavailable // TODO: check if available will propagate to unavailable
case DEVICE.DISCONNECT : case DEVICE.DISCONNECT:
case DEVICE.DISCONNECT_UNACQUIRED : case DEVICE.DISCONNECT_UNACQUIRED:
return disconnectDevice(state, action.device); return disconnectDevice(state, action.device);
case WALLET.SET_SELECTED_DEVICE : case WALLET.SET_SELECTED_DEVICE:
return onSelectedDevice(state, action.device); return onSelectedDevice(state, action.device);
default: default:
return state; return state;
} }
} }
// UTILS // UTILS
export const getNewInstance = (devices: State, device: Device | TrezorDevice): number => { export const getNewInstance = (devices: State, device: Device | TrezorDevice): number => {
const affectedDevices: State = devices.filter(d => d.features && device.features && d.features.device_id === device.features.device_id) const affectedDevices: State = devices.filter(d => d.features && device.features && d.features.device_id === device.features.device_id)
.sort((a, b) => { .sort((a, b) => {
if (!a.instance) { if (!a.instance) {
return -1; return -1;
} else { }
return !b.instance || a.instance > b.instance ? 1 : -1; return !b.instance || a.instance > b.instance ? 1 : -1;
} });
});
const instance: number = affectedDevices.reduce((inst, dev) => { const instance: number = affectedDevices.reduce((inst, dev) => (dev.instance ? dev.instance + 1 : inst + 1), 0);
return dev.instance ? dev.instance + 1 : inst + 1;
}, 0);
return instance; return instance;
} };

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import HDKey from 'hdkey'; import HDKey from 'hdkey';
@ -9,16 +9,16 @@ import * as CONNECT from '../actions/constants/TrezorConnect';
import * as WALLET from '../actions/constants/wallet'; import * as WALLET from '../actions/constants/wallet';
import type { Action, TrezorDevice } from '~/flowtype'; import type { Action, TrezorDevice } from '~/flowtype';
import type { import type {
DiscoveryStartAction, DiscoveryStartAction,
DiscoveryWaitingAction, DiscoveryWaitingAction,
DiscoveryStopAction, DiscoveryStopAction,
DiscoveryCompleteAction DiscoveryCompleteAction,
} from '../actions/DiscoveryActions'; } from '../actions/DiscoveryActions';
import type { import type {
AccountCreateAction AccountCreateAction,
} from '../actions/AccountsActions' } from '../actions/AccountsActions';
export type Discovery = { export type Discovery = {
network: string; network: string;
@ -37,13 +37,11 @@ export type Discovery = {
export type State = Array<Discovery>; export type State = Array<Discovery>;
const initialState: State = []; const initialState: State = [];
const findIndex = (state: State, network: string, deviceState: string): number => { const findIndex = (state: State, network: string, deviceState: string): number => state.findIndex(d => d.network === network && d.deviceState === deviceState);
return state.findIndex(d => d.network === network && d.deviceState === deviceState);
}
const start = (state: State, action: DiscoveryStartAction): State => { const start = (state: State, action: DiscoveryStartAction): State => {
const deviceState: string = action.device.state || '0'; const deviceState: string = action.device.state || '0';
const hdKey: HDKey = new HDKey(); const hdKey: HDKey = new HDKey();
hdKey.publicKey = new Buffer(action.publicKey, 'hex'); hdKey.publicKey = new Buffer(action.publicKey, 'hex');
hdKey.chainCode = new Buffer(action.chainCode, 'hex'); hdKey.chainCode = new Buffer(action.chainCode, 'hex');
const instance: Discovery = { const instance: Discovery = {
@ -58,9 +56,9 @@ const start = (state: State, action: DiscoveryStartAction): State => {
completed: false, completed: false,
waitingForDevice: false, waitingForDevice: false,
waitingForBackend: false, waitingForBackend: false,
} };
const newState: State = [ ...state ]; const newState: State = [...state];
const index: number = findIndex(state, action.network, deviceState); const index: number = findIndex(state, action.network, deviceState);
if (index >= 0) { if (index >= 0) {
newState[index] = instance; newState[index] = instance;
@ -68,47 +66,44 @@ const start = (state: State, action: DiscoveryStartAction): State => {
newState.push(instance); newState.push(instance);
} }
return newState; return newState;
} };
const complete = (state: State, action: DiscoveryCompleteAction): State => { const complete = (state: State, action: DiscoveryCompleteAction): State => {
const index: number = findIndex(state, action.network, action.device.state || '0'); const index: number = findIndex(state, action.network, action.device.state || '0');
const newState: State = [ ...state ]; const newState: State = [...state];
newState[index].completed = true; newState[index].completed = true;
return newState; return newState;
} };
const accountCreate = (state: State, action: AccountCreateAction): State => { const accountCreate = (state: State, action: AccountCreateAction): State => {
const index: number = findIndex(state, action.network, action.device.state || '0'); const index: number = findIndex(state, action.network, action.device.state || '0');
const newState: State = [ ...state ]; const newState: State = [...state];
newState[index].accountIndex++; newState[index].accountIndex++;
return newState; return newState;
} };
const forgetDiscovery = (state: State, device: TrezorDevice): State => { const forgetDiscovery = (state: State, device: TrezorDevice): State => state.filter(d => d.deviceState !== device.state);
return state.filter(d => d.deviceState !== device.state);
}
const clear = (state: State, devices: Array<TrezorDevice>): State => { const clear = (state: State, devices: Array<TrezorDevice>): State => {
let newState: State = [ ...state ]; let newState: State = [...state];
devices.forEach(d => { devices.forEach((d) => {
newState = forgetDiscovery(newState, d); newState = forgetDiscovery(newState, d);
}); });
return newState; return newState;
} };
const stop = (state: State, action: DiscoveryStopAction): State => { const stop = (state: State, action: DiscoveryStopAction): State => {
const newState: State = [ ...state ]; const newState: State = [...state];
return newState.map( (d: Discovery) => { return newState.map((d: Discovery) => {
if (d.deviceState === action.device.state && !d.completed) { if (d.deviceState === action.device.state && !d.completed) {
d.interrupted = true; d.interrupted = true;
d.waitingForDevice = false; d.waitingForDevice = false;
} }
return d; return d;
}); });
} };
const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State => { const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State => {
const deviceState: string = action.device.state || '0'; const deviceState: string = action.device.state || '0';
const instance: Discovery = { const instance: Discovery = {
network: action.network, network: action.network,
@ -122,10 +117,10 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State =
completed: false, completed: false,
waitingForDevice: true, waitingForDevice: true,
waitingForBackend: false, waitingForBackend: false,
} };
const index: number = findIndex(state, action.network, deviceState); const index: number = findIndex(state, action.network, deviceState);
const newState: State = [ ...state ]; const newState: State = [...state];
if (index >= 0) { if (index >= 0) {
newState[index] = instance; newState[index] = instance;
} else { } else {
@ -133,7 +128,7 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State =
} }
return newState; return newState;
} };
const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State => { const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State => {
const deviceState: string = action.device.state || '0'; const deviceState: string = action.device.state || '0';
@ -148,11 +143,11 @@ const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State
interrupted: false, interrupted: false,
completed: false, completed: false,
waitingForDevice: false, waitingForDevice: false,
waitingForBackend: true waitingForBackend: true,
} };
const index: number = findIndex(state, action.network, deviceState); const index: number = findIndex(state, action.network, deviceState);
const newState: State = [ ...state ]; const newState: State = [...state];
if (index >= 0) { if (index >= 0) {
newState[index] = instance; newState[index] = instance;
} else { } else {
@ -160,26 +155,25 @@ const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State
} }
return newState; return newState;
} };
export default function discovery(state: State = initialState, action: Action): State { export default function discovery(state: State = initialState, action: Action): State {
switch (action.type) { switch (action.type) {
case DISCOVERY.START : case DISCOVERY.START:
return start(state, action); return start(state, action);
case ACCOUNT.CREATE : case ACCOUNT.CREATE:
return accountCreate(state, action); return accountCreate(state, action);
case DISCOVERY.STOP : case DISCOVERY.STOP:
return stop(state, action); return stop(state, action);
case DISCOVERY.COMPLETE : case DISCOVERY.COMPLETE:
return complete(state, action); return complete(state, action);
case DISCOVERY.WAITING_FOR_DEVICE : case DISCOVERY.WAITING_FOR_DEVICE:
return waitingForDevice(state, action); return waitingForDevice(state, action);
case DISCOVERY.WAITING_FOR_BACKEND : case DISCOVERY.WAITING_FOR_BACKEND:
return waitingForBackend(state, action); return waitingForBackend(state, action);
case DISCOVERY.FROM_STORAGE : case DISCOVERY.FROM_STORAGE:
return action.payload.map(d => { return action.payload.map((d) => {
const hdKey: HDKey = new HDKey(); const hdKey: HDKey = new HDKey();
hdKey.publicKey = new Buffer(d.publicKey, 'hex'); hdKey.publicKey = new Buffer(d.publicKey, 'hex');
hdKey.chainCode = new Buffer(d.chainCode, 'hex'); hdKey.chainCode = new Buffer(d.chainCode, 'hex');
return { return {
@ -188,16 +182,15 @@ export default function discovery(state: State = initialState, action: Action):
interrupted: false, interrupted: false,
waitingForDevice: false, waitingForDevice: false,
waitingForBackend: false, waitingForBackend: false,
} };
}) });
case CONNECT.FORGET : case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE : case CONNECT.FORGET_SINGLE:
return forgetDiscovery(state, action.device); return forgetDiscovery(state, action.device);
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA : case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:
return clear(state, action.devices); return clear(state, action.devices);
default: default:
return state; return state;
} }
} }

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import { RATE_UPDATE } from '../services/CoinmarketcapService'; import { RATE_UPDATE } from '../services/CoinmarketcapService';
@ -14,29 +14,26 @@ export type Fiat = {
export const initialState: Array<Fiat> = []; export const initialState: Array<Fiat> = [];
const update = (state: Array<Fiat>, action: FiatRateAction): Array<Fiat> => { const update = (state: Array<Fiat>, action: FiatRateAction): Array<Fiat> => {
const newState: Array<Fiat> = [ ...state ]; const newState: Array<Fiat> = [...state];
const exists: ?Fiat = newState.find(f => f.network === action.network); const exists: ?Fiat = newState.find(f => f.network === action.network);
if (exists) { if (exists) {
exists.value = action.rate.price_usd; exists.value = action.rate.price_usd;
} else { } else {
newState.push({ newState.push({
network: action.network, network: action.network,
value: action.rate.price_usd value: action.rate.price_usd,
}) });
} }
return newState; return newState;
} };
export default (state: Array<Fiat> = initialState, action: Action): Array<Fiat> => { export default (state: Array<Fiat> = initialState, action: Action): Array<Fiat> => {
switch (action.type) { switch (action.type) {
case RATE_UPDATE:
case RATE_UPDATE :
return update(state, action); return update(state, action);
default: default:
return state; return state;
} }
};
}

@ -1,5 +1,5 @@
/* @flow */ /* @flow */
'use strict';
import * as STORAGE from '../actions/constants/localStorage'; import * as STORAGE from '../actions/constants/localStorage';
@ -80,42 +80,39 @@ const initialState: State = {
error: null, error: null,
config: { config: {
coins: [], coins: [],
fiatValueTickers: [] fiatValueTickers: [],
}, },
ERC20Abi: [], ERC20Abi: [],
tokens: {}, tokens: {},
customBackend: [ customBackend: [
{ {
name: "Custom1", name: 'Custom1',
slug: "custom1", slug: 'custom1',
url: "http://google.com" url: 'http://google.com',
} },
] ],
}; };
export default function localStorage(state: State = initialState, action: Action): State { export default function localStorage(state: State = initialState, action: Action): State {
switch (action.type) { switch (action.type) {
case STORAGE.READY:
case STORAGE.READY :
return { return {
...state, ...state,
initialized: true, initialized: true,
config: action.config, config: action.config,
ERC20Abi: action.ERC20Abi, ERC20Abi: action.ERC20Abi,
tokens: action.tokens, tokens: action.tokens,
error: null error: null,
} };
case STORAGE.ERROR : case STORAGE.ERROR:
return { return {
...state, ...state,
error: action.error error: action.error,
} };
default: default:
return state; return state;
} }
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save