lint all files

pull/420/head
Vladimir Volek 5 years ago
parent b13cbb1365
commit 749224dc66

@ -3,10 +3,10 @@
"eslint-config-airbnb",
"prettier",
"prettier/babel",
"plugin:flowtype/recommended",
"prettier/flowtype",
"prettier/react",
"plugin:jest/recommended",
"plugin:flowtype/recommended",
"plugin:jest/recommended"
],
"globals": {

@ -21,7 +21,8 @@
"lint": "run-s lint:*",
"lint:js": "npx eslint ./src ./webpack",
"lint:css": "npx stylelint './src/**/*.js'",
"lint:prettier": "pretty-quick",
"lint:prettier": "npx pretty-quick",
"lint-fix": "npx eslint ./src ./webpack --fix",
"prettier:check": "eslint --print-config ./src | eslint-config-prettier-check",
"test": "run-s test:*",
"test:unit": "npx jest",

@ -4,13 +4,15 @@ import * as ACCOUNT from 'actions/constants/account';
import type { Action } from 'flowtype';
import type { Account, State } from 'reducers/AccountsReducer';
export type AccountAction = {
type: typeof ACCOUNT.FROM_STORAGE,
payload: State
} | {
type: typeof ACCOUNT.CREATE | typeof ACCOUNT.UPDATE,
payload: Account,
};
export type AccountAction =
| {
type: typeof ACCOUNT.FROM_STORAGE,
payload: State,
}
| {
type: typeof ACCOUNT.CREATE | typeof ACCOUNT.UPDATE,
payload: Account,
};
export const update = (account: Account): Action => ({
type: ACCOUNT.UPDATE,

@ -4,35 +4,35 @@ import * as BLOCKCHAIN from 'actions/constants/blockchain';
import * as EthereumBlockchainActions from 'actions/ethereum/BlockchainActions';
import * as RippleBlockchainActions from 'actions/ripple/BlockchainActions';
import type {
Dispatch,
GetState,
PromiseAction,
BlockchainFeeLevel,
} from 'flowtype';
import type { Dispatch, GetState, PromiseAction, BlockchainFeeLevel } from 'flowtype';
import type { BlockchainBlock, BlockchainNotification, BlockchainError } from 'trezor-connect';
export type BlockchainAction = {
type: typeof BLOCKCHAIN.READY,
} | {
type: typeof BLOCKCHAIN.UPDATE_FEE,
shortcut: string,
feeLevels: Array<BlockchainFeeLevel>,
} | {
type: typeof BLOCKCHAIN.START_SUBSCRIBE,
shortcut: string,
}
export type BlockchainAction =
| {
type: typeof BLOCKCHAIN.READY,
}
| {
type: typeof BLOCKCHAIN.UPDATE_FEE,
shortcut: string,
feeLevels: Array<BlockchainFeeLevel>,
}
| {
type: typeof BLOCKCHAIN.START_SUBSCRIBE,
shortcut: string,
};
// Conditionally subscribe to blockchain backend
// called after TrezorConnect.init successfully emits TRANSPORT.START event
// checks if there are discovery processes loaded from LocalStorage
// if so starts subscription to proper networks
export const init = (): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const init = (): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
if (getState().discovery.length > 0) {
// get unique networks
const networks: Array<string> = [];
getState().discovery.forEach((discovery) => {
getState().discovery.forEach(discovery => {
if (networks.indexOf(discovery.network) < 0) {
networks.push(discovery.network);
}
@ -50,7 +50,10 @@ export const init = (): PromiseAction<void> => async (dispatch: Dispatch, getSta
});
};
export const subscribe = (networkName: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const subscribe = (networkName: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === networkName);
if (!network) return;
@ -67,11 +70,14 @@ export const subscribe = (networkName: string): PromiseAction<void> => async (di
case 'ripple':
await dispatch(RippleBlockchainActions.subscribe(networkName));
break;
default: break;
default:
break;
}
};
export const onBlockMined = (payload: $ElementType<BlockchainBlock, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onBlockMined = (
payload: $ElementType<BlockchainBlock, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const shortcut = payload.coin.shortcut.toLowerCase();
if (getState().router.location.state.network !== shortcut) return;
@ -86,11 +92,14 @@ export const onBlockMined = (payload: $ElementType<BlockchainBlock, 'payload'>):
case 'ripple':
await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
break;
default: break;
default:
break;
}
};
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onNotification = (
payload: $ElementType<BlockchainNotification, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const shortcut = payload.coin.shortcut.toLowerCase();
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === shortcut);
@ -104,13 +113,16 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
case 'ripple':
await dispatch(RippleBlockchainActions.onNotification(payload));
break;
default: break;
default:
break;
}
};
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
// disconnect and remove Web3 websocket instance if exists
export const onError = (payload: $ElementType<BlockchainError, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onError = (
payload: $ElementType<BlockchainError, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const shortcut = payload.coin.shortcut.toLowerCase();
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === shortcut);
@ -124,6 +136,7 @@ export const onError = (payload: $ElementType<BlockchainError, 'payload'>): Prom
// this error is handled in BlockchainReducer
// await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
break;
default: break;
default:
break;
}
};

@ -20,63 +20,85 @@ import * as BlockchainActions from './BlockchainActions';
import * as EthereumDiscoveryActions from './ethereum/DiscoveryActions';
import * as RippleDiscoveryActions from './ripple/DiscoveryActions';
export type DiscoveryStartAction = EthereumDiscoveryActions.DiscoveryStartAction | RippleDiscoveryActions.DiscoveryStartAction;
export type DiscoveryStartAction =
| EthereumDiscoveryActions.DiscoveryStartAction
| RippleDiscoveryActions.DiscoveryStartAction;
export type DiscoveryWaitingAction = {
type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN | typeof DISCOVERY.FIRMWARE_NOT_SUPPORTED | typeof DISCOVERY.FIRMWARE_OUTDATED,
type:
| typeof DISCOVERY.WAITING_FOR_DEVICE
| typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN
| typeof DISCOVERY.FIRMWARE_NOT_SUPPORTED
| typeof DISCOVERY.FIRMWARE_OUTDATED,
device: TrezorDevice,
network: string,
}
};
export type DiscoveryCompleteAction = {
type: typeof DISCOVERY.COMPLETE,
device: TrezorDevice,
network: string,
}
export type DiscoveryAction = {
type: typeof DISCOVERY.FROM_STORAGE,
payload: State
} | {
type: typeof DISCOVERY.STOP,
device: TrezorDevice
} | DiscoveryStartAction
| DiscoveryWaitingAction
| DiscoveryCompleteAction;
};
export type DiscoveryAction =
| {
type: typeof DISCOVERY.FROM_STORAGE,
payload: State,
}
| {
type: typeof DISCOVERY.STOP,
device: TrezorDevice,
}
| DiscoveryStartAction
| DiscoveryWaitingAction
| DiscoveryCompleteAction;
// There are multiple async methods during discovery process (trezor-connect and blockchain actions)
// This method will check after each of async action if process was interrupted (for example by network change or device disconnect)
const isProcessInterrupted = (process?: Discovery): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
const isProcessInterrupted = (process?: Discovery): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
if (!process) {
return false;
}
const { deviceState, network } = process;
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === deviceState && d.network === network);
const discoveryProcess: ?Discovery = getState().discovery.find(
d => d.deviceState === deviceState && d.network === network
);
if (!discoveryProcess) return false;
return discoveryProcess.interrupted;
};
// Private action
// Called from "this.begin", "this.restore", "this.addAccount"
const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const selected = getState().wallet.selectedDevice;
if (!selected) {
// TODO: throw error
console.error('Start discovery: no selected device', device);
return;
} if (selected.path !== device.path) {
}
if (selected.path !== device.path) {
console.error('Start discovery: requested device is not selected', device, selected);
return;
} if (!selected.state) {
}
if (!selected.state) {
console.warn("Start discovery: Selected device wasn't authenticated yet...");
return;
} if (selected.connected && !selected.available) {
}
if (selected.connected && !selected.available) {
console.warn('Start discovery: Selected device is unavailable...');
return;
}
const { discovery } = getState();
const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network);
const discoveryProcess: ?Discovery = discovery.find(
d => d.deviceState === device.state && d.network === network
);
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
dispatch({
@ -105,7 +127,11 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean)
device,
network,
});
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) {
} else if (
discoveryProcess.interrupted ||
discoveryProcess.waitingForDevice ||
discoveryProcess.waitingForBlockchain
) {
// discovery cycle was interrupted
// start from beginning
dispatch(begin(device, network));
@ -117,7 +143,10 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean)
// first iteration
// generate public key for this account
// start discovery process
const begin = (device: TrezorDevice, networkName: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const begin = (device: TrezorDevice, networkName: string): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === networkName);
if (!network) return;
@ -165,7 +194,9 @@ const begin = (device: TrezorDevice, networkName: string): AsyncAction => async
// check for interruption
// corner case: DISCOVERY.START wasn't called yet, but Discovery exists in reducer created by DISCOVERY.WAITING_FOR_DEVICE action
// this is why we need to get process instance directly from reducer
const discoveryProcess = getState().discovery.find(d => d.deviceState === device.state && d.network === network);
const discoveryProcess = getState().discovery.find(
d => d.deviceState === device.state && d.network === network
);
if (dispatch(isProcessInterrupted(discoveryProcess))) return;
// send data to reducer
@ -175,7 +206,10 @@ const begin = (device: TrezorDevice, networkName: string): AsyncAction => async
dispatch(start(device, networkName));
};
const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
if (!network) return;
@ -185,13 +219,19 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
try {
switch (network.type) {
case 'ethereum':
account = await dispatch(EthereumDiscoveryActions.discoverAccount(device, discoveryProcess));
account = await dispatch(
EthereumDiscoveryActions.discoverAccount(device, discoveryProcess)
);
break;
case 'ripple':
account = await dispatch(RippleDiscoveryActions.discoverAccount(device, discoveryProcess));
account = await dispatch(
RippleDiscoveryActions.discoverAccount(device, discoveryProcess)
);
break;
default:
throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`);
throw new Error(
`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`
);
}
} catch (error) {
// handle not supported firmware error
@ -242,7 +282,11 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
if (dispatch(isProcessInterrupted(discoveryProcess))) return;
const accountIsEmpty = account.empty;
if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && accountIndex === 0)) {
if (
!accountIsEmpty ||
(accountIsEmpty && completed) ||
(accountIsEmpty && accountIndex === 0)
) {
dispatch({
type: ACCOUNT.CREATE,
payload: account,
@ -256,7 +300,9 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
}
};
const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
await TrezorConnect.getFeatures({
device: {
path: device.path,
@ -279,7 +325,9 @@ const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction
});
};
export const reconnect = (network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
export const reconnect = (network: string): PromiseAction<void> => async (
dispatch: Dispatch
): Promise<void> => {
await dispatch(BlockchainActions.subscribe(network));
dispatch(restore());
};
@ -296,10 +344,16 @@ export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetStat
if (!selected) return;
// find discovery process for requested 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 there was no process befor OR process was interrupted/waiting
const shouldStart = !discoveryProcess || (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain);
const shouldStart =
!discoveryProcess ||
(discoveryProcess.interrupted ||
discoveryProcess.waitingForDevice ||
discoveryProcess.waitingForBlockchain);
if (shouldStart) {
dispatch(start(selected, urlParams.network));
}
@ -310,7 +364,9 @@ export const stop = (): ThunkAction => (dispatch: Dispatch, getState: GetState):
if (!device) return;
// get all uncompleted discovery processes which assigned to selected device
const discoveryProcesses = getState().discovery.filter(d => d.deviceState === device.state && !d.completed);
const discoveryProcesses = getState().discovery.filter(
d => d.deviceState === device.state && !d.completed
);
if (discoveryProcesses.length > 0) {
dispatch({
type: DISCOVERY.STOP,

@ -1,6 +1,5 @@
/* @flow */
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as ACCOUNT from 'actions/constants/account';
import * as TOKEN from 'actions/constants/token';
@ -32,18 +31,21 @@ import type { Config, Network, TokensCollection } from 'reducers/LocalStorageRed
import Erc20AbiJSON from 'public/data/ERC20Abi.json';
import AppConfigJSON from 'public/data/appConfig.json';
export type StorageAction = {
type: typeof STORAGE.READY,
config: Config,
tokens: TokensCollection,
ERC20Abi: Array<TokensCollection>
} | {
type: typeof STORAGE.SAVE,
network: string,
} | {
type: typeof STORAGE.ERROR,
error: string,
};
export type StorageAction =
| {
type: typeof STORAGE.READY,
config: Config,
tokens: TokensCollection,
ERC20Abi: Array<TokensCollection>,
}
| {
type: typeof STORAGE.SAVE,
network: string,
}
| {
type: typeof STORAGE.ERROR,
error: string,
};
const TYPE: 'local' = 'local';
const { STORAGE_PATH } = storageUtils;
@ -60,16 +62,39 @@ const KEY_LANGUAGE: string = `${STORAGE_PATH}language`;
// or
// https://www.npmjs.com/package/redux-react-session
const findAccounts = (devices: Array<TrezorDevice>, accounts: Array<Account>): Array<Account> => devices.reduce((arr, dev) => arr.concat(accounts.filter(a => a.deviceState === dev.state)), []);
const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token> => accounts.reduce((arr, account) => arr.concat(getAccountTokens(tokens, account)), []);
const findDiscovery = (devices: Array<TrezorDevice>, discovery: Array<Discovery>): Array<Discovery> => devices.reduce((arr, dev) => arr.concat(discovery.filter(d => d.deviceState === dev.state && d.completed)), []);
const findPendingTxs = (accounts: Array<Account>, pending: Array<Transaction>): Array<Transaction> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.descriptor === account.descriptor && p.network === account.network)), []);
const findAccounts = (devices: Array<TrezorDevice>, accounts: Array<Account>): Array<Account> =>
devices.reduce((arr, dev) => arr.concat(accounts.filter(a => a.deviceState === dev.state)), []);
const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token> =>
accounts.reduce((arr, account) => arr.concat(getAccountTokens(tokens, account)), []);
const findDiscovery = (
devices: Array<TrezorDevice>,
discovery: Array<Discovery>
): Array<Discovery> =>
devices.reduce(
(arr, dev) => arr.concat(discovery.filter(d => d.deviceState === dev.state && d.completed)),
[]
);
const findPendingTxs = (
accounts: Array<Account>,
pending: Array<Transaction>
): Array<Transaction> =>
accounts.reduce(
(result, account) =>
result.concat(
pending.filter(
p => p.descriptor === account.descriptor && p.network === account.network
)
),
[]
);
export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const devices: Array<TrezorDevice> = getState().devices.filter(d => d.features && d.remember === true);
const devices: Array<TrezorDevice> = getState().devices.filter(
d => d.features && d.remember === true
);
const accounts: Array<Account> = findAccounts(devices, getState().accounts);
const tokens: Array<Token> = findTokens(accounts, getState().tokens);
const pending: Array<Transaction> = findPendingTxs(accounts, getState().pending);
@ -98,14 +123,12 @@ export const update = (event: StorageEvent): ThunkAction => (dispatch: Dispatch)
// check if device was added/ removed
// const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue);
// const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features);
// if (newDevices.length !== myDevices.length) {
// const diff = myDevices.filter(d => newDevices.indexOf(d) < 0)
// console.warn("DEV LIST CHANGED!", newDevices.length, myDevices.length, diff)
// // check if difference is caused by local device which is not saved
// // or device which was saved in other tab
// }
// const diff = oldDevices.filter(d => newDevices.indexOf())
}
@ -151,19 +174,25 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> =>
const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json');
window.addEventListener('storage', (event) => {
window.addEventListener('storage', event => {
dispatch(update(event));
});
// load tokens
const tokens = await config.networks.reduce(async (promise: Promise<TokensCollection>, network: Network): Promise<TokensCollection> => {
const collection: TokensCollection = await promise;
if (network.tokens) {
const json = await httpRequest(network.tokens, 'json');
collection[network.shortcut] = json;
}
return collection;
}, Promise.resolve({}));
const tokens = await config.networks.reduce(
async (
promise: Promise<TokensCollection>,
network: Network
): Promise<TokensCollection> => {
const collection: TokensCollection = await promise;
if (network.tokens) {
const json = await httpRequest(network.tokens, 'json');
collection[network.shortcut] = json;
}
return collection;
},
Promise.resolve({})
);
dispatch({
type: STORAGE.READY,
@ -230,7 +259,9 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
}
if (buildUtils.isDev() || buildUtils.isBeta()) {
const betaModal = Object.keys(window.localStorage).find(key => key.indexOf(KEY_BETA_MODAL) >= 0);
const betaModal = Object.keys(window.localStorage).find(
key => key.indexOf(KEY_BETA_MODAL) >= 0
);
if (!betaModal) {
dispatch({
type: WALLET.SHOW_BETA_DISCLAIMER,

@ -2,19 +2,20 @@
import * as LOG from 'actions/constants/log';
import type {
Action, ThunkAction, GetState, Dispatch,
} from 'flowtype';
import type { Action, ThunkAction, GetState, Dispatch } from 'flowtype';
import type { LogEntry } from 'reducers/LogReducer';
export type LogAction = {
type: typeof LOG.OPEN,
} | {
type: typeof LOG.CLOSE,
} | {
type: typeof LOG.ADD,
payload: LogEntry
};
export type LogAction =
| {
type: typeof LOG.OPEN,
}
| {
type: typeof LOG.CLOSE,
}
| {
type: typeof LOG.ADD,
payload: LogEntry,
};
export const toggle = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
if (!getState().log.opened) {

@ -6,25 +6,25 @@ import type { Device } from 'trezor-connect';
import * as MODAL from 'actions/constants/modal';
import * as CONNECT from 'actions/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 { parsedURI } from 'utils/cryptoUriParser';
import sendEthereumFormActions from './ethereum/SendFormActions';
import sendRippleFormActions from './ripple/SendFormActions';
export type ModalAction = {
type: typeof MODAL.CLOSE
} | {
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
id: string,
url: string,
} | {
type: typeof MODAL.OPEN_SCAN_QR,
};
export type ModalAction =
| {
type: typeof MODAL.CLOSE,
}
| {
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
id: string,
url: string,
}
| {
type: typeof MODAL.OPEN_SCAN_QR,
};
export const onPinSubmit = (value: string): Action => {
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
@ -33,7 +33,10 @@ export const onPinSubmit = (value: string): Action => {
};
};
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { modal } = getState();
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
@ -59,7 +62,9 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (di
});
};
export const onReceiveConfirmation = (confirmation: any): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
export const onReceiveConfirmation = (confirmation: any): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
await TrezorConnect.uiResponse({
type: UI.RECEIVE_CONFIRMATION,
payload: confirmation,
@ -89,7 +94,9 @@ export const onCancel = (): Action => ({
type: MODAL.CLOSE,
});
export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (
dispatch: Dispatch
): void => {
dispatch(onCancel());
dispatch({
@ -98,11 +105,17 @@ export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatc
});
};
export const onRememberRequest = (prevState: State): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
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.context === MODAL.CONTEXT_DEVICE && prevState.windowType === CONNECT.REMEMBER_REQUEST) {
if (
prevState.context === MODAL.CONTEXT_DEVICE &&
prevState.windowType === CONNECT.REMEMBER_REQUEST
) {
// forget current (new)
if (state.context === MODAL.CONTEXT_DEVICE) {
dispatch({
@ -119,12 +132,20 @@ export const onRememberRequest = (prevState: State): ThunkAction => (dispatch: D
}
};
export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const onDeviceConnect = (device: Device): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
// interrupt process of remembering device (force forget)
// TODO: the same for disconnect more than 1 device at once
const { modal } = getState();
if (modal.context === MODAL.CONTEXT_DEVICE && modal.windowType === CONNECT.REMEMBER_REQUEST) {
if (device.features && modal.device && modal.device.features && modal.device.features.device_id === device.features.device_id) {
if (
device.features &&
modal.device &&
modal.device.features &&
modal.device.features.device_id === device.features.device_id
) {
dispatch({
type: MODAL.CLOSE,
});
@ -137,7 +158,10 @@ export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispa
}
};
export const onWalletTypeRequest = (hidden: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const onWalletTypeRequest = (hidden: boolean): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { modal } = getState();
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
dispatch({
@ -150,7 +174,9 @@ export const onWalletTypeRequest = (hidden: boolean): ThunkAction => (dispatch:
});
};
export const gotoExternalWallet = (id: string, url: string): ThunkAction => (dispatch: Dispatch): void => {
export const gotoExternalWallet = (id: string, url: string): ThunkAction => (
dispatch: Dispatch
): void => {
dispatch({
type: MODAL.OPEN_EXTERNAL_WALLET,
id,
@ -164,7 +190,9 @@ export const openQrModal = (): ThunkAction => (dispatch: Dispatch): void => {
});
};
export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction => (dispatch: Dispatch): void => {
export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction => (
dispatch: Dispatch
): void => {
const { address = '', amount } = parsedUri;
switch (networkType) {
case 'ethereum':
@ -180,7 +208,6 @@ export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction
}
};
export default {
onPinSubmit,
onPassphraseSubmit,

@ -2,41 +2,48 @@
import * as React from 'react';
import * as NOTIFICATION from 'actions/constants/notification';
import type {
Action, AsyncAction, GetState, Dispatch, RouterLocationState,
} from 'flowtype';
import type { Action, AsyncAction, GetState, Dispatch, RouterLocationState } from 'flowtype';
import type { CallbackAction } from 'reducers/NotificationReducer';
export type NotificationAction = {
type: typeof NOTIFICATION.ADD,
payload: {
+type: string,
+title: React.Node | string,
+message?: ?(React.Node | string),
+cancelable: boolean,
actions?: Array<CallbackAction>
}
} | {
type: typeof NOTIFICATION.CLOSE,
payload?: {
id?: string;
devicePath?: string
}
}
export type NotificationAction =
| {
type: typeof NOTIFICATION.ADD,
payload: {
+type: string,
+title: React.Node | string,
+message?: ?(React.Node | string),
+cancelable: boolean,
actions?: Array<CallbackAction>,
},
}
| {
type: typeof NOTIFICATION.CLOSE,
payload?: {
id?: string,
devicePath?: string,
},
};
export const close = (payload: any = {}): Action => ({
type: NOTIFICATION.CLOSE,
payload,
});
// called from RouterService
export const clear = (currentParams: RouterLocationState, requestedParams: RouterLocationState): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const clear = (
currentParams: RouterLocationState,
requestedParams: RouterLocationState
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
// if route has been changed from device view into something else (like other device, settings...)
// try to remove all Notifications which are linked to previous device (they are not cancelable by user)
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string');
entries.forEach((entry) => {
if (
currentParams.device !== requestedParams.device ||
currentParams.deviceInstance !== requestedParams.deviceInstance
) {
const entries = getState().notifications.filter(
entry => typeof entry.devicePath === 'string'
);
entries.forEach(entry => {
if (typeof entry.devicePath === 'string') {
dispatch({
type: NOTIFICATION.CLOSE,

@ -1,24 +1,28 @@
/* @flow */
import * as PENDING from 'actions/constants/pendingTx';
import type { Transaction } from 'flowtype';
import type { State } from 'reducers/PendingTxReducer';
export type PendingTxAction = {
type: typeof PENDING.FROM_STORAGE,
payload: State
} | {
type: typeof PENDING.ADD,
payload: Transaction
} | {
type: typeof PENDING.TX_RESOLVED,
hash: string,
} | {
type: typeof PENDING.TX_REJECTED,
hash: string,
} | {
type: typeof PENDING.TX_TOKEN_ERROR,
hash: string,
}
export type PendingTxAction =
| {
type: typeof PENDING.FROM_STORAGE,
payload: State,
}
| {
type: typeof PENDING.ADD,
payload: Transaction,
}
| {
type: typeof PENDING.TX_RESOLVED,
hash: string,
}
| {
type: typeof PENDING.TX_REJECTED,
hash: string,
}
| {
type: typeof PENDING.TX_TOKEN_ERROR,
hash: string,
};

@ -1,6 +1,5 @@
/* @flow */
import TrezorConnect from 'trezor-connect';
import * as RECEIVE from 'actions/constants/receive';
import * as NOTIFICATION from 'actions/constants/notification';
@ -8,25 +7,29 @@ import * as NOTIFICATION from 'actions/constants/notification';
import { initialState } from 'reducers/ReceiveReducer';
import type { State } from 'reducers/ReceiveReducer';
import type {
TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch,
} from 'flowtype';
export type ReceiveAction = {
type: typeof RECEIVE.INIT,
state: State
} | {
type: typeof RECEIVE.DISPOSE,
} | {
type: typeof RECEIVE.REQUEST_UNVERIFIED,
device: TrezorDevice
} | {
type: typeof RECEIVE.SHOW_ADDRESS
} | {
type: typeof RECEIVE.HIDE_ADDRESS
} | {
type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS
}
import type { TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch } from 'flowtype';
export type ReceiveAction =
| {
type: typeof RECEIVE.INIT,
state: State,
}
| {
type: typeof RECEIVE.DISPOSE,
}
| {
type: typeof RECEIVE.REQUEST_UNVERIFIED,
device: TrezorDevice,
}
| {
type: typeof RECEIVE.SHOW_ADDRESS,
}
| {
type: typeof RECEIVE.HIDE_ADDRESS,
}
| {
type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS,
};
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
const state: State = {
@ -48,7 +51,10 @@ export const showUnverifiedAddress = (): Action => ({
});
//export const showAddress = (address_n: string): AsyncAction => {
export const showAddress = (path: Array<number>): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const showAddress = (path: Array<number>): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
const { network } = getState().selectedAccount;
@ -81,7 +87,11 @@ export const showAddress = (path: Array<number>): AsyncAction => async (dispatch
response = await TrezorConnect.rippleGetAddress(params);
break;
default:
response = { payload: { error: `ReceiveActions.showAddress: Unknown network type: ${network.type}` } };
response = {
payload: {
error: `ReceiveActions.showAddress: Unknown network type: ${network.type}`,
},
};
break;
}

@ -18,9 +18,11 @@ import type {
import type { RouterAction } from 'connected-react-router';
/*
* Parse url string to RouterLocationState object (key/value)
*/
export const pathToParams = (path: string): PayloadAction<RouterLocationState> => (): RouterLocationState => {
* Parse url string to RouterLocationState object (key/value)
*/
export const pathToParams = (
path: string
): PayloadAction<RouterLocationState> => (): RouterLocationState => {
// split url into parts
const parts: Array<string> = path.split('/').slice(1);
const params: RouterLocationState = {};
@ -46,10 +48,13 @@ export const pathToParams = (path: string): PayloadAction<RouterLocationState> =
};
/*
* RouterLocationState validation
* Check if requested device or network exists in reducers
*/
export const paramsValidation = (params: RouterLocationState): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* RouterLocationState validation
* Check if requested device or network exists in reducers
*/
export const paramsValidation = (params: RouterLocationState): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
// validate requested device
if (params.hasOwnProperty('device')) {
@ -57,9 +62,18 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
let device: ?TrezorDevice;
if (params.hasOwnProperty('deviceInstance')) {
device = devices.find(d => d.features && d.features.device_id === params.device && d.instance === parseInt(params.deviceInstance, 10));
device = devices.find(
d =>
d.features &&
d.features.device_id === params.device &&
d.instance === parseInt(params.deviceInstance, 10)
);
} else {
device = devices.find(d => ((!d.features || d.mode === 'bootloader') && d.path === params.device) || (d.features && d.features.device_id === params.device));
device = devices.find(
d =>
((!d.features || d.mode === 'bootloader') && d.path === params.device) ||
(d.features && d.features.device_id === params.device)
);
}
if (!device) return false;
@ -87,12 +101,16 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
};
/*
* Composing url string from given RouterLocationState object
* Filters unrecognized fields and sorting in correct order
*/
export const paramsToPath = (params: RouterLocationState): PayloadAction<?string> => (): ?string => {
* Composing url string from given RouterLocationState object
* Filters unrecognized fields and sorting in correct order
*/
export const paramsToPath = (
params: RouterLocationState
): PayloadAction<?string> => (): ?string => {
// get patterns (fields) from routes and sort them by complexity
const patterns: Array<Array<string>> = routes.map(r => r.fields).sort((a, b) => (a.length > b.length ? -1 : 1));
const patterns: Array<Array<string>> = routes
.map(r => r.fields)
.sort((a, b) => (a.length > b.length ? -1 : 1));
// find pattern
const keys: Array<string> = Object.keys(params);
@ -111,7 +129,7 @@ export const paramsToPath = (params: RouterLocationState): PayloadAction<?string
// compose url string from pattern
let url: string = '';
patternToUse.forEach((field) => {
patternToUse.forEach(field => {
if (field === params[field]) {
// standalone (odd) fields
url += `/${field}`;
@ -127,7 +145,10 @@ export const paramsToPath = (params: RouterLocationState): PayloadAction<?string
return url;
};
export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dispatch: Dispatch, getState: GetState): string => {
export const getValidUrl = (action: RouterAction): PayloadAction<string> => (
dispatch: Dispatch,
getState: GetState
): string => {
const { location } = getState().router;
const { firstLocationChange } = getState().wallet;
// redirect to landing page (loading screen)
@ -151,12 +172,17 @@ export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dis
// example 2 (invalid blocking): url changes while passphrase modal opened because device disconnect
const currentParams = dispatch(pathToParams(location.pathname));
const currentParamsAreValid = dispatch(paramsValidation(currentParams));
if (currentParamsAreValid) { return location.pathname; }
if (currentParamsAreValid) {
return location.pathname;
}
}
// there are no connected devices or application isn't ready or initialization error occurred
// redirect to landing page
const shouldBeLandingPage = getState().devices.length < 1 || !getState().wallet.ready || getState().connect.error !== null;
const shouldBeLandingPage =
getState().devices.length < 1 ||
!getState().wallet.ready ||
getState().connect.error !== null;
const landingPageUrl = dispatch(isLandingPageUrl(requestedUrl));
if (shouldBeLandingPage) {
const landingPageRoute = dispatch(isLandingPageUrl(requestedUrl, getState().wallet.ready));
@ -185,13 +211,17 @@ export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dis
};
/*
* Compose url from requested device object and returns url
*/
const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> => (dispatch: Dispatch, getState: GetState): ?string => {
* Compose url from requested device object and returns url
*/
const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> => (
dispatch: Dispatch,
getState: GetState
): ?string => {
let url: ?string;
if (!device.features) {
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
} else if (device.mode === 'bootloader') { // device in bootloader doesn't have device_id
} else if (device.mode === 'bootloader') {
// device in bootloader doesn't have device_id
url = `/device/${device.path}/bootloader`;
} else if (device.mode === 'initialize') {
url = `/device/${device.features.device_id}/initialize`;
@ -207,7 +237,9 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
if (!device.hasOwnProperty('ts')) {
// it is device from trezor-connect triggered by DEVICE.CONNECT event
// need to lookup if there are unavailable instances
const available: Array<TrezorDevice> = getState().devices.filter(d => d.path === device.path);
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}`;
@ -218,13 +250,16 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
};
/*
* Try to find first available device using order:
* 1. First unacquired
* 2. First connected
* 3. Saved with latest timestamp
* OR redirect to landing page
*/
export const getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (dispatch: Dispatch, getState: GetState): ?string => {
* Try to find first available device using order:
* 1. First unacquired
* 2. First connected
* 3. Saved with latest timestamp
* OR redirect to landing page
*/
export const getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (
dispatch: Dispatch,
getState: GetState
): ?string => {
const { devices } = getState();
let url: ?string;
if (devices.length > 0) {
@ -241,20 +276,24 @@ export const getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (dispatc
};
/*
* Utility used in "getDeviceUrl" and "getFirstAvailableDeviceUrl"
* sorting device array by "ts" (timestamp) field
*/
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => devices.sort((a, b) => {
if (!a.ts || !b.ts) {
return -1;
}
return a.ts > b.ts ? -1 : 1;
});
* Utility used in "getDeviceUrl" and "getFirstAvailableDeviceUrl"
* sorting device array by "ts" (timestamp) field
*/
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> =>
devices.sort((a, b) => {
if (!a.ts || !b.ts) {
return -1;
}
return a.ts > b.ts ? -1 : 1;
});
/*
* Redirect to requested device
*/
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Redirect to requested device
*/
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
if (dispatch(setInitialUrl())) return;
const url: ?string = dispatch(getDeviceUrl(device));
@ -262,20 +301,30 @@ export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dis
const currentParams: RouterLocationState = getState().router.location.state;
const requestedParams = dispatch(pathToParams(url));
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
if (
currentParams.device !== requestedParams.device ||
currentParams.deviceInstance !== requestedParams.deviceInstance
) {
dispatch(goto(url));
}
};
/*
* Redirect to first device or landing page
*/
export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Redirect to first device or landing page
*/
export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const url = dispatch(getFirstAvailableDeviceUrl());
if (url) {
const currentParams = getState().router.location.state;
const requestedParams = dispatch(pathToParams(url));
if (gotoRoot || currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
if (
gotoRoot ||
currentParams.device !== requestedParams.device ||
currentParams.deviceInstance !== requestedParams.deviceInstance
) {
dispatch(goto(url));
}
} else {
@ -284,8 +333,8 @@ export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkActi
};
/*
* Internal method. redirect to given url
*/
* Internal method. redirect to given url
*/
const goto = (url: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
if (getState().router.location.pathname !== url) {
dispatch(push(url));
@ -293,15 +342,20 @@ const goto = (url: string): ThunkAction => (dispatch: Dispatch, getState: GetSta
};
/*
* Check if requested OR current url is landing page
*/
export const isLandingPageUrl = ($url?: string, checkRoutes: boolean = false): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* Check if requested OR current url is landing page
*/
export const isLandingPageUrl = (
$url?: string,
checkRoutes: boolean = false
): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
let url: ?string = $url;
if (typeof url !== 'string') {
url = getState().router.location.pathname;
}
if (checkRoutes) {
const isLandingRoute = routes.find(r => r.pattern === url && r.name.indexOf('landing') >= 0);
const isLandingRoute = routes.find(
r => r.pattern === url && r.name.indexOf('landing') >= 0
);
if (isLandingRoute) {
return true;
}
@ -310,8 +364,8 @@ export const isLandingPageUrl = ($url?: string, checkRoutes: boolean = false): P
};
/*
* Try to redirect to landing page
*/
* Try to redirect to landing page
*/
export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void => {
const isLandingPage = dispatch(isLandingPageUrl());
if (!isLandingPage) {
@ -320,61 +374,76 @@ export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void =>
};
/*
* Go to given device settings page
*/
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
* Go to given device settings page
*/
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (
dispatch: Dispatch
): void => {
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(goto(`/device/${devUrl}/settings`));
}
};
/*
* Go to UpdateBridge page
*/
* Go to UpdateBridge page
*/
export const gotoBridgeUpdate = (): ThunkAction => (dispatch: Dispatch): void => {
dispatch(goto('/bridge'));
};
/*
* Go to UpdateFirmware page
* Called from App notification
*/
export const gotoFirmwareUpdate = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Go to UpdateFirmware page
* Called from App notification
*/
export const gotoFirmwareUpdate = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { selectedDevice } = getState().wallet;
if (!selectedDevice || !selectedDevice.features) return;
const devUrl: string = `${selectedDevice.features.device_id}${selectedDevice.instance ? `:${selectedDevice.instance}` : ''}`;
const devUrl: string = `${selectedDevice.features.device_id}${
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
}`;
dispatch(goto(`/device/${devUrl}/firmware-update`));
};
/*
* Go to NoBackup page
*/
* Go to NoBackup page
*/
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const { selectedDevice } = getState().wallet;
if (!selectedDevice || !selectedDevice.features) return;
const devUrl: string = `${selectedDevice.features.device_id}${selectedDevice.instance ? `:${selectedDevice.instance}` : ''}`;
const devUrl: string = `${selectedDevice.features.device_id}${
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
}`;
dispatch(goto(`/device/${devUrl}/backup`));
};
/*
* Try to redirect to initial url
*/
export const setInitialUrl = (): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* Try to redirect to initial url
*/
export const setInitialUrl = (): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
const { initialPathname } = getState().wallet;
if (typeof initialPathname === 'string' && !dispatch(isLandingPageUrl(initialPathname, true))) {
const valid = dispatch(getValidUrl({
type: LOCATION_CHANGE,
payload: {
location: {
pathname: initialPathname,
hash: '',
search: '',
state: {},
const valid = dispatch(
getValidUrl({
type: LOCATION_CHANGE,
payload: {
location: {
pathname: initialPathname,
hash: '',
search: '',
state: {},
},
},
},
}));
})
);
if (valid === initialPathname) {
// reset initial url

@ -11,13 +11,7 @@ import * as reducerUtils from 'reducers/utils';
import { getVersion } from 'utils/device';
import { initialState } from 'reducers/SelectedAccountReducer';
import type {
PayloadAction,
Action,
GetState,
Dispatch,
State,
} from 'flowtype';
import type { PayloadAction, Action, GetState, Dispatch, State } from 'flowtype';
import type {
State as SelectedAccountState,
@ -26,12 +20,14 @@ import type {
ExceptionPage,
} from 'reducers/SelectedAccountReducer';
export type SelectedAccountAction = {
type: typeof ACCOUNT.DISPOSE,
} | {
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload: SelectedAccountState,
};
export type SelectedAccountAction =
| {
type: typeof ACCOUNT.DISPOSE,
}
| {
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload: SelectedAccountState,
};
export const dispose = (): Action => ({
type: ACCOUNT.DISPOSE,
@ -68,11 +64,7 @@ const getExceptionPage = (state: State, selectedAccount: SelectedAccountState):
// display loader instead of component body
const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?Loader => {
const device = state.wallet.selectedDevice;
const {
account,
discovery,
network,
} = selectedAccount;
const { account, discovery, network } = selectedAccount;
if (!device || !device.state) {
return {
@ -89,7 +81,6 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState):
};
}
if (account) return null;
// account not found (yet). checking why...
@ -135,7 +126,10 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState):
};
// display notification above the component, with or without component body
const getAccountNotification = (state: State, selectedAccount: SelectedAccountState): ?(Notification & { shouldRender: boolean }) => {
const getAccountNotification = (
state: State,
selectedAccount: SelectedAccountState
): ?(Notification & { shouldRender: boolean }) => {
const device = state.wallet.selectedDevice;
const { account, network, discovery } = selectedAccount;
if (!device || !network) return null;
@ -190,16 +184,21 @@ const actions = [
...Object.values(BLOCKCHAIN).filter(v => typeof v === 'string'),
WALLET.SET_SELECTED_DEVICE,
WALLET.UPDATE_SELECTED_DEVICE,
...Object.values(ACCOUNT).filter(v => typeof v === 'string' && v !== ACCOUNT.UPDATE_SELECTED_ACCOUNT && v !== ACCOUNT.DISPOSE), // exported values got unwanted "__esModule: true" as first element
...Object.values(ACCOUNT).filter(
v => typeof v === 'string' && v !== ACCOUNT.UPDATE_SELECTED_ACCOUNT && v !== ACCOUNT.DISPOSE
), // exported values got unwanted "__esModule: true" as first element
...Object.values(DISCOVERY).filter(v => typeof v === 'string'),
...Object.values(TOKEN).filter(v => typeof v === 'string'),
...Object.values(PENDING).filter(v => typeof v === 'string'),
];
/*
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
// ignore not listed actions
if (actions.indexOf(action.type) < 0) return false;
const state: State = getState();
@ -238,12 +237,22 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
newState.notification = notification;
}
newState.shouldRender = !(loader || exceptionPage || (notification && !notification.shouldRender));
newState.shouldRender = !(
loader ||
exceptionPage ||
(notification && !notification.shouldRender)
);
// check if newState is different than previous state
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, {
account: ['balance', 'nonce'],
discovery: ['accountIndex', 'interrupted', 'completed', 'waitingForBlockchain', 'waitingForDevice'],
discovery: [
'accountIndex',
'interrupted',
'completed',
'waitingForBlockchain',
'waitingForDevice',
],
});
if (stateChanged) {

@ -4,33 +4,30 @@ import * as SEND from 'actions/constants/send';
import * as WEB3 from 'actions/constants/web3';
import * as BLOCKCHAIN from 'actions/constants/blockchain';
import type {
Dispatch,
GetState,
State as ReducersState,
Action,
ThunkAction,
} from 'flowtype';
import type { Dispatch, GetState, State as ReducersState, Action, ThunkAction } from 'flowtype';
import type { State as EthereumState } from 'reducers/SendFormEthereumReducer';
import type { State as RippleState } from 'reducers/SendFormRippleReducer';
import * as EthereumSendFormActions from './ethereum/SendFormActions';
import * as RippleSendFormActions from './ripple/SendFormActions';
export type SendFormAction = {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ethereum',
state: EthereumState,
} | {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ripple',
state: RippleState,
} | {
type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR,
} | {
type: typeof SEND.TX_COMPLETE,
};
export type SendFormAction =
| {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ethereum',
state: EthereumState,
}
| {
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
networkType: 'ripple',
state: RippleState,
}
| {
type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR,
}
| {
type: typeof SEND.TX_COMPLETE,
};
// list of all actions which has influence on "sendForm" reducer
// other actions will be ignored
@ -42,9 +39,12 @@ const actions = [
];
/*
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
// ignore not listed actions
if (actions.indexOf(action.type) < 0) return;
@ -62,6 +62,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
case 'ripple':
dispatch(RippleSendFormActions.observe(prevState, action));
break;
default: break;
default:
break;
}
};

@ -4,12 +4,7 @@ import { findToken } from 'reducers/utils';
import type { State as EthereumSendFormState } from 'reducers/SendFormEthereumReducer';
import type { State as RippleSendFormState } from 'reducers/SendFormRippleReducer';
import type {
ThunkAction,
PayloadAction,
GetState,
Dispatch,
} from 'flowtype';
import type { ThunkAction, PayloadAction, GetState, Dispatch } from 'flowtype';
const TYPE: 'session' = 'session';
const { STORAGE_PATH } = storageUtils;
@ -20,7 +15,10 @@ const getTxDraftKey = (getState: GetState): string => {
return `${KEY_TX_DRAFT}${pathname}`;
};
export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const saveDraftTransaction = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state = getState().sendFormEthereum;
if (state.untouched) return;
@ -28,7 +26,10 @@ export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getS
storageUtils.set(TYPE, key, JSON.stringify(state));
};
export const loadEthereumDraftTransaction = (): PayloadAction<?EthereumSendFormState> => (dispatch: Dispatch, getState: GetState): ?EthereumSendFormState => {
export const loadEthereumDraftTransaction = (): PayloadAction<?EthereumSendFormState> => (
dispatch: Dispatch,
getState: GetState
): ?EthereumSendFormState => {
const key = getTxDraftKey(getState);
const value: ?string = storageUtils.get(TYPE, key);
if (!value) return null;
@ -53,7 +54,10 @@ export const loadEthereumDraftTransaction = (): PayloadAction<?EthereumSendFormS
return state;
};
export const loadRippleDraftTransaction = (): PayloadAction<?RippleSendFormState> => (dispatch: Dispatch, getState: GetState): ?RippleSendFormState => {
export const loadRippleDraftTransaction = (): PayloadAction<?RippleSendFormState> => (
dispatch: Dispatch,
getState: GetState
): ?RippleSendFormState => {
const key = getTxDraftKey(getState);
const value: ?string = storageUtils.get(TYPE, key);
if (!value) return null;

@ -1,41 +1,45 @@
/* @flow */
import TrezorConnect from 'trezor-connect';
import type {
GetState, Dispatch, ThunkAction, AsyncAction,
} from 'flowtype';
import type { GetState, Dispatch, ThunkAction, AsyncAction } from 'flowtype';
import { validateAddress } from 'utils/ethUtils';
import * as NOTIFICATION from 'actions/constants/notification';
import * as SIGN_VERIFY from './constants/signVerify';
export type SignVerifyAction = {
type: typeof SIGN_VERIFY.SIGN_SUCCESS,
signSignature: string
} | {
type: typeof SIGN_VERIFY.CLEAR_SIGN,
} | {
type: typeof SIGN_VERIFY.CLEAR_VERIFY,
} | {
type: typeof SIGN_VERIFY.INPUT_CHANGE,
inputName: string,
value: string
} | {
type: typeof SIGN_VERIFY.TOUCH,
inputName: string,
} | {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string
} | {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string
}
export type SignVerifyAction =
| {
type: typeof SIGN_VERIFY.SIGN_SUCCESS,
signSignature: string,
}
| {
type: typeof SIGN_VERIFY.CLEAR_SIGN,
}
| {
type: typeof SIGN_VERIFY.CLEAR_VERIFY,
}
| {
type: typeof SIGN_VERIFY.INPUT_CHANGE,
inputName: string,
value: string,
}
| {
type: typeof SIGN_VERIFY.TOUCH,
inputName: string,
}
| {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string,
}
| {
type: typeof SIGN_VERIFY.ERROR,
inputName: string,
message: ?string,
};
const sign = (
path: Array<number>,
message: string,
hex: boolean = false,
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const sign = (path: Array<number>, message: string, hex: boolean = false): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
@ -73,7 +77,7 @@ const verify = (
address: string,
message: string,
signature: string,
hex: boolean = false,
hex: boolean = false
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
@ -125,7 +129,9 @@ const verify = (
}
};
const inputChange = (inputName: string, value: string): ThunkAction => (dispatch: Dispatch): void => {
const inputChange = (inputName: string, value: string): ThunkAction => (
dispatch: Dispatch
): void => {
dispatch({
type: SIGN_VERIFY.INPUT_CHANGE,
inputName,

@ -2,19 +2,20 @@
import * as SUMMARY from 'actions/constants/summary';
import { initialState } from 'reducers/SummaryReducer';
import type {
ThunkAction, Action, Dispatch,
} from 'flowtype';
import type { ThunkAction, Action, Dispatch } from 'flowtype';
import type { State } from 'reducers/SummaryReducer';
export type SummaryAction = {
type: typeof SUMMARY.INIT,
state: State
} | {
type: typeof SUMMARY.DISPOSE,
} | {
type: typeof SUMMARY.DETAILS_TOGGLE
}
export type SummaryAction =
| {
type: typeof SUMMARY.INIT,
state: State,
}
| {
type: typeof SUMMARY.DISPOSE,
}
| {
type: typeof SUMMARY.DETAILS_TOGGLE,
};
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
const state: State = {

@ -1,41 +1,47 @@
/* @flow */
import * as TOKEN from 'actions/constants/token';
import type {
GetState, AsyncAction, Action, Dispatch,
} from 'flowtype';
import type { GetState, AsyncAction, Action, Dispatch } from 'flowtype';
import type { State, Token } from 'reducers/TokensReducer';
import type { Account } from 'reducers/AccountsReducer';
import type { NetworkToken } from 'reducers/LocalStorageReducer';
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
export type TokenAction = {
type: typeof TOKEN.FROM_STORAGE,
payload: State
} | {
type: typeof TOKEN.ADD,
payload: Token
} | {
type: typeof TOKEN.REMOVE,
token: Token
} | {
type: typeof TOKEN.SET_BALANCE,
payload: State
}
export type TokenAction =
| {
type: typeof TOKEN.FROM_STORAGE,
payload: State,
}
| {
type: typeof TOKEN.ADD,
payload: Token,
}
| {
type: typeof TOKEN.REMOVE,
token: Token,
}
| {
type: typeof TOKEN.SET_BALANCE,
payload: State,
};
// action from component <reactSelect>
export const load = ($input: string, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<any> => {
export const load = ($input: string, network: string): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<any> => {
let input = $input;
if (input.length < 1) input = '0x';
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);
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) {
// TODO: Temporary fix for async select
@ -52,23 +58,33 @@ export const load = ($input: string, network: string): AsyncAction => async (dis
return null;
};
export const setBalance = (tokenAddress: string, ethAddress: string, balance: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
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);
const token: ?Token = newState.find(
t => t.address === tokenAddress && t.ethAddress === ethAddress
);
if (token) {
const others = newState.filter(t => t !== token);
dispatch({
type: TOKEN.SET_BALANCE,
payload: others.concat([{
...token,
loaded: true,
balance,
}]),
payload: others.concat([
{
...token,
loaded: true,
balance,
},
]),
});
}
};
export const add = (token: NetworkToken, account: Account): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
export const add = (token: NetworkToken, account: Account): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
const tkn: Token = {
loaded: false,
deviceState: account.deviceState,

@ -1,6 +1,10 @@
/* @flow */
import TrezorConnect, {
DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT,
DEVICE,
DEVICE_EVENT,
UI_EVENT,
TRANSPORT_EVENT,
BLOCKCHAIN_EVENT,
} from 'trezor-connect';
import { CONTEXT_NONE } from 'actions/constants/modal';
import urlConstants from 'constants/urls';
@ -31,96 +35,127 @@ import type {
TrezorDevice,
} from 'flowtype';
export type TrezorConnectAction = {
type: typeof CONNECT.INITIALIZATION_ERROR,
error: string
} | {
type: typeof CONNECT.NETWORK_CHANGED,
payload: {
network: string
}
} | {
type: typeof CONNECT.AUTH_DEVICE,
device: TrezorDevice,
state: string
} | {
type: typeof CONNECT.DUPLICATE,
device: TrezorDevice
} | {
type: typeof CONNECT.REMEMBER_REQUEST,
device: TrezorDevice,
instances: Array<TrezorDevice>
} | {
type: typeof CONNECT.DISCONNECT_REQUEST,
device: TrezorDevice
} | {
type: typeof CONNECT.FORGET_REQUEST,
device: TrezorDevice
} | {
type: typeof CONNECT.FORGET,
device: TrezorDevice
} | {
type: typeof CONNECT.FORGET_SINGLE | typeof CONNECT.FORGET_SILENT,
device: TrezorDevice
} | {
type: typeof CONNECT.REMEMBER,
device: TrezorDevice
} | {
type: typeof CONNECT.TRY_TO_DUPLICATE,
device: TrezorDevice
} | {
type: typeof CONNECT.DEVICE_FROM_STORAGE,
payload: Array<TrezorDevice>
} | {
type: typeof CONNECT.START_ACQUIRING | typeof CONNECT.STOP_ACQUIRING,
} | {
type: typeof CONNECT.REQUEST_WALLET_TYPE,
device: TrezorDevice
} | {
type: typeof CONNECT.RECEIVE_WALLET_TYPE | typeof CONNECT.UPDATE_WALLET_TYPE,
device: TrezorDevice,
hidden: boolean,
};
export type TrezorConnectAction =
| {
type: typeof CONNECT.INITIALIZATION_ERROR,
error: string,
}
| {
type: typeof CONNECT.NETWORK_CHANGED,
payload: {
network: string,
},
}
| {
type: typeof CONNECT.AUTH_DEVICE,
device: TrezorDevice,
state: string,
}
| {
type: typeof CONNECT.DUPLICATE,
device: TrezorDevice,
}
| {
type: typeof CONNECT.REMEMBER_REQUEST,
device: TrezorDevice,
instances: Array<TrezorDevice>,
}
| {
type: typeof CONNECT.DISCONNECT_REQUEST,
device: TrezorDevice,
}
| {
type: typeof CONNECT.FORGET_REQUEST,
device: TrezorDevice,
}
| {
type: typeof CONNECT.FORGET,
device: TrezorDevice,
}
| {
type: typeof CONNECT.FORGET_SINGLE | typeof CONNECT.FORGET_SILENT,
device: TrezorDevice,
}
| {
type: typeof CONNECT.REMEMBER,
device: TrezorDevice,
}
| {
type: typeof CONNECT.TRY_TO_DUPLICATE,
device: TrezorDevice,
}
| {
type: typeof CONNECT.DEVICE_FROM_STORAGE,
payload: Array<TrezorDevice>,
}
| {
type: typeof CONNECT.START_ACQUIRING | typeof CONNECT.STOP_ACQUIRING,
}
| {
type: typeof CONNECT.REQUEST_WALLET_TYPE,
device: TrezorDevice,
}
| {
type: typeof CONNECT.RECEIVE_WALLET_TYPE | typeof CONNECT.UPDATE_WALLET_TYPE,
device: TrezorDevice,
hidden: boolean,
};
declare var LOCAL: ?string;
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const init = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
// set listeners
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
// post event to reducers
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
device: event.payload,
});
});
TrezorConnect.on(
DEVICE_EVENT,
(event: DeviceMessage): void => {
// post event to reducers
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
device: event.payload,
});
}
);
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
// post event to reducers
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
});
TrezorConnect.on(
UI_EVENT,
(event: UiMessage): void => {
// post event to reducers
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
}
);
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
// post event to reducers
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
});
TrezorConnect.on(
TRANSPORT_EVENT,
(event: TransportMessage): void => {
// post event to reducers
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
dispatch({
type,
payload: event.payload,
});
}
);
// post event to reducers
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainEvent): void => {
dispatch(event);
});
TrezorConnect.on(
BLOCKCHAIN_EVENT,
(event: BlockchainEvent): void => {
dispatch(event);
}
);
if (buildUtils.isDev()) {
window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/'; // eslint-disable-line no-underscore-dangle
// eslint-disable-next-line
window.__TREZOR_CONNECT_SRC =
typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/'; // eslint-disable-line no-underscore-dangle
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://localhost:8088/'; // eslint-disable-line no-underscore-dangle
window.TrezorConnect = TrezorConnect;
}
@ -131,7 +166,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
debug: false,
popup: false,
webusb: true,
pendingTransportEvent: (getState().devices.length < 1),
pendingTransportEvent: getState().devices.length < 1,
manifest: {
email: 'info@trezor.io',
appUrl: urlConstants.NEXT_WALLET,
@ -165,10 +200,18 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch): void => {
}
};
export const requestWalletType = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const requestWalletType = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
const isDeviceReady =
selected.connected &&
selected.features &&
!selected.state &&
selected.mode === 'normal' &&
selected.firmware !== 'required';
if (!isDeviceReady) return;
if (selected.features && selected.features.passphrase_protection) {
@ -186,10 +229,18 @@ export const requestWalletType = (): AsyncAction => async (dispatch: Dispatch, g
}
};
export const authorizeDevice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const authorizeDevice = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
const isDeviceReady =
selected.connected &&
selected.features &&
!selected.state &&
selected.mode === 'normal' &&
selected.firmware !== 'required';
if (!isDeviceReady) return;
const response = await TrezorConnect.getDeviceState({
@ -233,11 +284,24 @@ export const authorizeDevice = (): AsyncAction => async (dispatch: Dispatch, get
}
};
export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const deviceDisconnect = (device: Device): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
if (device.features) {
const instances = getState().devices.filter(d => d.features && device.features && d.state && !d.remember && d.features.device_id === device.features.device_id);
const instances = getState().devices.filter(
d =>
d.features &&
device.features &&
d.state &&
!d.remember &&
d.features.device_id === device.features.device_id
);
if (instances.length > 0) {
const isSelected = deviceUtils.isSelectedDevice(getState().wallet.selectedDevice, device);
const isSelected = deviceUtils.isSelectedDevice(
getState().wallet.selectedDevice,
device
);
if (!isSelected && getState().modal.context !== CONTEXT_NONE) {
dispatch({
type: CONNECT.FORGET_SILENT,
@ -259,8 +323,7 @@ export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch
};
export function reload(): AsyncAction {
return async (): Promise<void> => {
};
return async (): Promise<void> => {};
}
export function acquire(): AsyncAction {
@ -314,7 +377,10 @@ export const forget = (device: TrezorDevice): Action => ({
device,
});
export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const instance: number = getDuplicateInstanceNumber(getState().devices, device);
const extended: Object = { instance };
dispatch({
@ -323,7 +389,9 @@ export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (
});
};
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (
dispatch: Dispatch
): Promise<void> => {
dispatch({
type: CONNECT.REQUEST_WALLET_TYPE,
device,

@ -7,27 +7,28 @@ import { toHex } from 'web3-utils'; // eslint-disable-line import/no-extraneous-
import { initWeb3 } from 'actions/Web3Actions';
import * as ethUtils from 'utils/ethUtils';
import type {
Dispatch,
PromiseAction,
} from 'flowtype';
import type { Dispatch, PromiseAction } from 'flowtype';
import type { EthereumTransaction } from 'trezor-connect';
import type { Token } from 'reducers/TokensReducer';
type EthereumTxRequest = {
network: string;
token: ?Token;
from: string;
to: string;
amount: string;
data: string;
gasLimit: string;
gasPrice: string;
nonce: number;
}
network: string,
token: ?Token,
from: string,
to: string,
amount: string,
data: string,
gasLimit: string,
gasPrice: string,
nonce: number,
};
export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<EthereumTransaction> => async (dispatch: Dispatch): Promise<EthereumTransaction> => {
export const prepareEthereumTx = (
tx: EthereumTxRequest
): PromiseAction<EthereumTransaction> => async (
dispatch: Dispatch
): Promise<EthereumTransaction> => {
const instance = await dispatch(initWeb3(tx.network));
const { token } = tx;
let data: string = ethUtils.sanitizeHex(tx.data);
@ -37,7 +38,9 @@ export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<Ethereum
// smart contract transaction
const contract = instance.erc20.clone();
contract.options.address = token.address;
const tokenAmount: string = new BigNumber(tx.amount).times(10 ** token.decimals).toString(10);
const tokenAmount: string = new BigNumber(tx.amount)
.times(10 ** token.decimals)
.toString(10);
data = instance.erc20.methods.transfer(to, tokenAmount).encodeABI();
value = '0x00';
to = token.address;
@ -57,7 +60,9 @@ export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<Ethereum
};
};
export const serializeEthereumTx = (tx: EthereumTransaction): PromiseAction<string> => async (): Promise<string> => {
export const serializeEthereumTx = (
tx: EthereumTransaction
): PromiseAction<string> => async (): Promise<string> => {
const ethTx = new EthereumjsTx(tx);
return `0x${ethTx.serialize().toString('hex')}`;
};

@ -19,34 +19,46 @@ import type {
State,
} from 'flowtype';
export type WalletAction = {
type: typeof WALLET.SET_INITIAL_URL,
state?: RouterLocationState,
pathname?: string
} | {
type: typeof WALLET.TOGGLE_DEVICE_DROPDOWN,
opened: boolean
} | {
type: typeof WALLET.ONLINE_STATUS,
online: boolean
} | {
type: typeof WALLET.SET_SELECTED_DEVICE,
device: ?TrezorDevice
} | {
type: typeof WALLET.UPDATE_SELECTED_DEVICE,
device: TrezorDevice
} | {
type: typeof WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
devices: Array<TrezorDevice>
} | {
type: typeof WALLET.SHOW_BETA_DISCLAIMER | typeof WALLET.HIDE_BETA_DISCLAIMER | typeof WALLET.SET_FIRST_LOCATION_CHANGE,
} | {
type: typeof WALLET.TOGGLE_SIDEBAR,
} | {
type: typeof WALLET.SET_LANGUAGE,
locale: string,
messages: { [string]: string },
}
export type WalletAction =
| {
type: typeof WALLET.SET_INITIAL_URL,
state?: RouterLocationState,
pathname?: string,
}
| {
type: typeof WALLET.TOGGLE_DEVICE_DROPDOWN,
opened: boolean,
}
| {
type: typeof WALLET.ONLINE_STATUS,
online: boolean,
}
| {
type: typeof WALLET.SET_SELECTED_DEVICE,
device: ?TrezorDevice,
}
| {
type: typeof WALLET.UPDATE_SELECTED_DEVICE,
device: TrezorDevice,
}
| {
type: typeof WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
devices: Array<TrezorDevice>,
}
| {
type:
| typeof WALLET.SHOW_BETA_DISCLAIMER
| typeof WALLET.HIDE_BETA_DISCLAIMER
| typeof WALLET.SET_FIRST_LOCATION_CHANGE,
}
| {
type: typeof WALLET.TOGGLE_SIDEBAR,
}
| {
type: typeof WALLET.SET_LANGUAGE,
locale: string,
messages: { [string]: string },
};
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
const updateOnlineStatus = () => {
@ -75,7 +87,7 @@ export const toggleSidebar = (): WalletAction => ({
export const fetchLocale = (locale: string): ThunkAction => (dispatch: Dispatch): void => {
fetch(`/l10n/${locale}.json`)
.then(response => response.json())
.then((messages) => {
.then(messages => {
dispatch({
type: WALLET.SET_LANGUAGE,
locale,
@ -89,12 +101,18 @@ export const fetchLocale = (locale: string): ThunkAction => (dispatch: Dispatch)
// all saved instances will be removed immediately inside DevicesReducer
// This method will clear leftovers associated with removed instances from reducers.
// (DiscoveryReducer, AccountReducer, TokensReducer)
export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (dispatch: Dispatch): void => {
export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (
dispatch: Dispatch
): void => {
if (!device.features) return;
const affectedDevices = prevState.devices.filter(d => d.features && device.features
&& d.features.device_id === device.features.device_id
&& d.features.passphrase_protection !== device.features.passphrase_protection);
const affectedDevices = prevState.devices.filter(
d =>
d.features &&
device.features &&
d.features.device_id === device.features.device_id &&
d.features.passphrase_protection !== device.features.passphrase_protection
);
if (affectedDevices.length > 0) {
dispatch({
@ -114,15 +132,21 @@ const actions = [
];
/*
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
* Called from WalletService
*/
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (
dispatch: Dispatch,
getState: GetState
): boolean => {
// ignore not listed actions
if (actions.indexOf(action.type) < 0) return false;
const state: State = getState();
const locationChanged = reducerUtils.observeChanges(prevState.router.location, state.router.location);
const locationChanged = reducerUtils.observeChanges(
prevState.router.location,
state.router.location
);
const device = reducerUtils.getSelectedDevice(state);
const selectedDeviceChanged = reducerUtils.observeChanges(state.wallet.selectedDevice, device);

@ -9,12 +9,7 @@ import * as WEB3 from 'actions/constants/web3';
import * as PENDING from 'actions/constants/pendingTx';
import * as ethUtils from 'utils/ethUtils';
import type {
Dispatch,
GetState,
ThunkAction,
PromiseAction,
} from 'flowtype';
import type { Dispatch, GetState, ThunkAction, PromiseAction } from 'flowtype';
import type { EthereumAccount } from 'trezor-connect';
import type { Account } from 'reducers/AccountsReducer';
@ -27,100 +22,114 @@ import * as AccountsActions from './AccountsActions';
export type Web3UpdateBlockAction = {
type: typeof WEB3.BLOCK_UPDATED,
network: string,
blockHash: string
blockHash: string,
};
export type Web3UpdateGasPriceAction = {
type: typeof WEB3.GAS_PRICE_UPDATED,
network: string,
gasPrice: string
gasPrice: string,
};
export type Web3Action = {
type: typeof WEB3.READY,
} | {
type: typeof WEB3.START,
} | {
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
instance: Web3Instance
} | Web3UpdateBlockAction
| Web3UpdateGasPriceAction;
export const initWeb3 = (network: string, urlIndex: number = 0): PromiseAction<Web3Instance> => async (dispatch: Dispatch, getState: GetState): Promise<Web3Instance> => new Promise(async (resolve, reject) => {
// check if requested web was initialized before
const instance = getState().web3.find(w3 => w3.network === network);
if (instance && instance.web3.currentProvider.connected) {
resolve(instance);
return;
}
// requested web3 wasn't initialized or is disconnected
// initialize again
const { config, ERC20Abi } = getState().localStorage;
const networkData = config.networks.find(c => c.shortcut === network);
if (!networkData) {
// network not found
reject(new Error(`Network ${network} not found in application config.`));
return;
}
export type Web3Action =
| {
type: typeof WEB3.READY,
}
| {
type: typeof WEB3.START,
}
| {
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
instance: Web3Instance,
}
| Web3UpdateBlockAction
| Web3UpdateGasPriceAction;
export const initWeb3 = (
network: string,
urlIndex: number = 0
): PromiseAction<Web3Instance> => async (
dispatch: Dispatch,
getState: GetState
): Promise<Web3Instance> =>
new Promise(async (resolve, reject) => {
// check if requested web was initialized before
const instance = getState().web3.find(w3 => w3.network === network);
if (instance && instance.web3.currentProvider.connected) {
resolve(instance);
return;
}
// get first url
const url = networkData.web3[urlIndex];
if (!url) {
reject(new Error('Web3 backend is not responding'));
return;
}
// requested web3 wasn't initialized or is disconnected
// initialize again
const { config, ERC20Abi } = getState().localStorage;
const networkData = config.networks.find(c => c.shortcut === network);
if (!networkData) {
// network not found
reject(new Error(`Network ${network} not found in application config.`));
return;
}
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
// get first url
const url = networkData.web3[urlIndex];
if (!url) {
reject(new Error('Web3 backend is not responding'));
return;
}
const onConnect = async () => {
const latestBlock = await web3.eth.getBlockNumber();
const gasPrice = await web3.eth.getGasPrice();
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
const newInstance = {
network,
web3,
chainId: networkData.chainId,
erc20: new web3.eth.Contract(ERC20Abi),
latestBlock,
gasPrice,
};
const onConnect = async () => {
const latestBlock = await web3.eth.getBlockNumber();
const gasPrice = await web3.eth.getGasPrice();
dispatch({
type: WEB3.CREATE,
instance: newInstance,
});
const newInstance = {
network,
web3,
chainId: networkData.chainId,
erc20: new web3.eth.Contract(ERC20Abi),
latestBlock,
gasPrice,
};
resolve(newInstance);
};
dispatch({
type: WEB3.CREATE,
instance: newInstance,
});
const onEnd = async () => {
web3.currentProvider.reset();
const oldInstance = getState().web3.find(w3 => w3.network === network);
resolve(newInstance);
};
if (oldInstance && oldInstance.web3.currentProvider.connected) {
// backend disconnects
// dispatch({
// type: 'WEB3.DISCONNECT',
// network
// });
} else {
// backend initialization error for given url, try next one
try {
const otherWeb3 = await dispatch(initWeb3(network, urlIndex + 1));
resolve(otherWeb3);
} catch (error) {
reject(error);
const onEnd = async () => {
web3.currentProvider.reset();
const oldInstance = getState().web3.find(w3 => w3.network === network);
if (oldInstance && oldInstance.web3.currentProvider.connected) {
// backend disconnects
// dispatch({
// type: 'WEB3.DISCONNECT',
// network
// });
} else {
// backend initialization error for given url, try next one
try {
const otherWeb3 = await dispatch(initWeb3(network, urlIndex + 1));
resolve(otherWeb3);
} catch (error) {
reject(error);
}
}
}
};
};
web3.currentProvider.on('connect', onConnect);
web3.currentProvider.on('end', onEnd);
web3.currentProvider.on('error', onEnd);
});
web3.currentProvider.on('connect', onConnect);
web3.currentProvider.on('end', onEnd);
web3.currentProvider.on('error', onEnd);
});
export const discoverAccount = (descriptor: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
export const discoverAccount = (
descriptor: string,
network: string
): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const balance = await instance.web3.eth.getBalance(descriptor);
const nonce = await instance.web3.eth.getTransactionCount(descriptor);
@ -134,10 +143,13 @@ export const discoverAccount = (descriptor: string, network: string): PromiseAct
};
};
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const pending = getState().pending.filter(p => p.network === network);
pending.forEach(async (tx) => {
pending.forEach(async tx => {
const status = await instance.web3.eth.getTransaction(tx.hash);
if (!status) {
dispatch({
@ -192,39 +204,49 @@ export const getTxInput = (): PromiseAction<void> => async (dispatch: Dispatch):
};
*/
export const updateAccount = (account: Account, newAccount: EthereumAccount, network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
export const updateAccount = (
account: Account,
newAccount: EthereumAccount,
network: string
): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const balance = await instance.web3.eth.getBalance(account.descriptor);
const nonce = await instance.web3.eth.getTransactionCount(account.descriptor);
dispatch(AccountsActions.update({
networkType: 'ethereum',
...account,
...newAccount,
nonce,
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
}));
dispatch(
AccountsActions.update({
networkType: 'ethereum',
...account,
...newAccount,
nonce,
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
})
);
// update tokens for this account
dispatch(updateAccountTokens(account));
};
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const tokens = getState().tokens.filter(t => t.network === account.network && t.ethAddress === account.descriptor);
tokens.forEach(async (token) => {
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const tokens = getState().tokens.filter(
t => t.network === account.network && t.ethAddress === account.descriptor
);
tokens.forEach(async token => {
const balance = await dispatch(getTokenBalance(token));
// const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
if (balance !== token.balance) {
dispatch(TokenActions.setBalance(
token.address,
token.ethAddress,
balance,
));
dispatch(TokenActions.setBalance(token.address, token.ethAddress, balance));
}
});
};
export const getTokenInfo = (address: string, network: string): PromiseAction<NetworkToken> => async (dispatch: Dispatch): Promise<NetworkToken> => {
export const getTokenInfo = (
address: string,
network: string
): PromiseAction<NetworkToken> => async (dispatch: Dispatch): Promise<NetworkToken> => {
const instance: Web3Instance = await dispatch(initWeb3(network));
const contract = instance.erc20.clone();
contract.options.address = address;
@ -241,7 +263,9 @@ export const getTokenInfo = (address: string, network: string): PromiseAction<Ne
};
};
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
export const getTokenBalance = (token: Token): PromiseAction<string> => async (
dispatch: Dispatch
): Promise<string> => {
const instance = await dispatch(initWeb3(token.network));
const contract = instance.erc20.clone();
contract.options.address = token.address;
@ -250,7 +274,10 @@ export const getTokenBalance = (token: Token): PromiseAction<string> => async (d
return new BigNumber(balance).dividedBy(10 ** token.decimals).toString(10);
};
export const getCurrentGasPrice = (network: string): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
export const getCurrentGasPrice = (network: string): PromiseAction<string> => async (
dispatch: Dispatch,
getState: GetState
): Promise<string> => {
const instance = getState().web3.find(w3 => w3.network === network);
if (instance) {
return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei');
@ -258,7 +285,9 @@ export const getCurrentGasPrice = (network: string): PromiseAction<string> => as
return '0';
};
export const updateGasPrice = (network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
export const updateGasPrice = (network: string): PromiseAction<void> => async (
dispatch: Dispatch
): Promise<void> => {
try {
const instance = await dispatch(initWeb3(network));
const gasPrice = await instance.web3.eth.getGasPrice();
@ -275,23 +304,32 @@ export const updateGasPrice = (network: string): PromiseAction<void> => async (d
}
};
export const estimateGasLimit = (network: string, $options: EstimateGasOptions): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
export const estimateGasLimit = (
network: string,
$options: EstimateGasOptions
): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
const instance = await dispatch(initWeb3(network));
const data = ethUtils.sanitizeHex($options.data);
const options = {
...$options,
to: '0x0000000000000000000000000000000000000000',
data,
value: instance.web3.utils.toHex(EthereumjsUnits.convert($options.value || '0', 'ether', 'wei')),
gasPrice: instance.web3.utils.toHex(EthereumjsUnits.convert($options.gasPrice, 'gwei', 'wei')),
value: instance.web3.utils.toHex(
EthereumjsUnits.convert($options.value || '0', 'ether', 'wei')
),
gasPrice: instance.web3.utils.toHex(
EthereumjsUnits.convert($options.gasPrice, 'gwei', 'wei')
),
};
const limit = await instance.web3.eth.estimateGas(options);
return limit.toString();
};
export const disconnect = (network: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const disconnect = (network: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
// check if Web3 was already initialized
const instance = getState().web3.find(w3 => w3.network === network);
if (instance) {

@ -1,13 +1,11 @@
/* @flow */
//regExp1 : string = '(.*)'
//regExp2 : '$1' = '$1'
export const READY: 'connect__ready' = 'connect__ready';
export const INITIALIZATION_ERROR: 'connect__init_error' = 'connect__init_error';
export const DEVICE_FROM_STORAGE: 'connect__device_from_storage' = 'connect__device_from_storage';
export const AUTH_DEVICE: 'connect__auth_device' = 'connect__auth_device';
export const NETWORK_CHANGED: 'connect__network_changed' = 'connect__network_changed';
@ -23,7 +21,8 @@ export const REMEMBER: 'connect__remember' = 'connect__remember';
export const TRY_TO_DUPLICATE: 'connect__try_to_duplicate' = 'connect__try_to_duplicate';
export const DUPLICATE: 'connect__duplicate' = 'connect__duplicate';
export const DEVICE_STATE_EXCEPTION: 'connect__device_state_exception' = 'connect__device_state_exception';
export const DEVICE_STATE_EXCEPTION: 'connect__device_state_exception' =
'connect__device_state_exception';
export const START_ACQUIRING: 'connect__start_acquiring' = 'connect__start_acquiring';
export const STOP_ACQUIRING: 'connect__stop_acquiring' = 'connect__stop_acquiring';

@ -1,6 +1,5 @@
/* @flow */
export const INIT: 'account__init' = 'account__init';
export const DISPOSE: 'account__dispose' = 'account__dispose';
@ -11,4 +10,5 @@ export const SET_BALANCE: 'account__set_balance' = 'account__set_balance';
export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce';
export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage';
export const UPDATE_SELECTED_ACCOUNT: 'account__update_selected_account' = 'account__update_selected_account';
export const UPDATE_SELECTED_ACCOUNT: 'account__update_selected_account' =
'account__update_selected_account';

@ -1,11 +1,11 @@
/* @flow */
export const START: 'discovery__start' = 'discovery__start';
export const STOP: 'discovery__stop' = 'discovery__stop';
export const FIRMWARE_NOT_SUPPORTED: 'discovery__fw_not_supported' = 'discovery__fw_not_supported';
export const FIRMWARE_OUTDATED: 'discovery__fw_outdated' = 'discovery__fw_outdated';
export const COMPLETE: 'discovery__complete' = 'discovery__complete';
export const WAITING_FOR_DEVICE: 'discovery__waiting_for_device' = 'discovery__waiting_for_device';
export const WAITING_FOR_BLOCKCHAIN: 'discovery__waiting_for_blockchain' = 'discovery__waiting_for_blockchain';
export const WAITING_FOR_BLOCKCHAIN: 'discovery__waiting_for_blockchain' =
'discovery__waiting_for_blockchain';
export const FROM_STORAGE: 'discovery__from_storage' = 'discovery__from_storage';

@ -1,6 +1,5 @@
/* @flow */
export const SAVE: 'storage__save' = 'storage__save';
export const READY: 'storage__ready' = 'storage__ready';
export const ERROR: 'storage__error' = 'storage__error';

@ -1,6 +1,5 @@
/* @flow */
export const OPEN: 'log__open' = 'log__open';
export const CLOSE: 'log__close' = 'log__close';
export const ADD: 'log__add' = 'log__add';

@ -1,6 +1,5 @@
/* @flow */
export const ADD: 'notification__add' = 'notification__add';
export const CLOSE: 'notification__close' = 'notification__close';
export const REMOVE: 'account__remove' = 'account__remove';

@ -1,6 +1,5 @@
/* @flow */
export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage';
export const ADD: 'pending__add' = 'pending__add';
export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved';

@ -1,6 +1,5 @@
/* @flow */
export const INIT: 'receive__init' = 'receive__init';
export const DISPOSE: 'receive__dispose' = 'receive__dispose';
export const REQUEST_UNVERIFIED: 'receive__request_unverified' = 'receive__request_unverified';

@ -1,6 +1,5 @@
/* @flow */
export const INIT: 'summary__init' = 'summary__init';
export const DISPOSE: 'summary__dispose' = 'summary__dispose';
export const ADD_TOKEN: 'summary__add_token' = 'summary__add_token';

@ -1,6 +1,5 @@
/* @flow */
export const ADD: 'token__add' = 'token__add';
export const REMOVE: 'token__remove' = 'token__remove';
export const SET_BALANCE: 'token__set_balance' = 'token__set_balance';

@ -2,15 +2,18 @@
export const TOGGLE_DEVICE_DROPDOWN: 'wallet__toggle_dropdown' = 'wallet__toggle_dropdown';
export const SET_INITIAL_URL: 'wallet__set_initial_url' = 'wallet__set_initial_url';
export const SET_FIRST_LOCATION_CHANGE: 'wallet__set_first_location_change' = 'wallet__set_first_location_change';
export const SET_FIRST_LOCATION_CHANGE: 'wallet__set_first_location_change' =
'wallet__set_first_location_change';
export const ONLINE_STATUS: 'wallet__online_status' = 'wallet__online_status';
export const SET_SELECTED_DEVICE: 'wallet__set_selected_device' = 'wallet__set_selected_device';
export const UPDATE_SELECTED_DEVICE: 'wallet__update_selected_device' = 'wallet__update_selected_device';
export const UPDATE_SELECTED_DEVICE: 'wallet__update_selected_device' =
'wallet__update_selected_device';
export const SHOW_BETA_DISCLAIMER: 'wallet__show_beta_disclaimer' = 'wallet__show_beta_disclaimer';
export const HIDE_BETA_DISCLAIMER: 'wallet__hide_beta_disclaimer' = 'wallet__hide_beta_disclaimer';
export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_data' = 'wallet__clear_unavailable_device_data';
export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_data' =
'wallet__clear_unavailable_device_data';
export const TOGGLE_SIDEBAR: 'wallet__toggle_sidebar' = 'wallet__toggle_sidebar';
export const SET_LANGUAGE: 'wallet__set_language' = 'wallet__set_language';

@ -1,6 +1,5 @@
/* @flow */
export const START: 'web3__start' = 'web3__start';
export const STOP: 'web3__stop' = 'web3__stop';
export const CREATE: 'web3__create' = 'web3__create';

@ -4,19 +4,18 @@ import TrezorConnect from 'trezor-connect';
import BigNumber from 'bignumber.js';
import * as PENDING from 'actions/constants/pendingTx';
import type {
TrezorDevice,
Dispatch,
GetState,
PromiseAction,
} from 'flowtype';
import type { TrezorDevice, Dispatch, GetState, PromiseAction } from 'flowtype';
import type { EthereumAccount, BlockchainNotification } from 'trezor-connect';
import type { Token } from 'reducers/TokensReducer';
import type { NetworkToken } from 'reducers/LocalStorageReducer';
import * as Web3Actions from 'actions/Web3Actions';
import * as AccountsActions from 'actions/AccountsActions';
export const discoverAccount = (device: TrezorDevice, descriptor: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
export const discoverAccount = (
device: TrezorDevice,
descriptor: string,
network: string
): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
// get data from connect
const txs = await TrezorConnect.ethereumGetAccountInfo({
account: {
@ -46,11 +45,18 @@ export const discoverAccount = (device: TrezorDevice, descriptor: string, networ
};
};
export const getTokenInfo = (input: string, network: string): PromiseAction<NetworkToken> => async (dispatch: Dispatch): Promise<NetworkToken> => dispatch(Web3Actions.getTokenInfo(input, network));
export const getTokenInfo = (input: string, network: string): PromiseAction<NetworkToken> => async (
dispatch: Dispatch
): Promise<NetworkToken> => dispatch(Web3Actions.getTokenInfo(input, network));
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => dispatch(Web3Actions.getTokenBalance(token));
export const getTokenBalance = (token: Token): PromiseAction<string> => async (
dispatch: Dispatch
): Promise<string> => dispatch(Web3Actions.getTokenBalance(token));
export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAction<BigNumber> => async (dispatch: Dispatch): Promise<BigNumber> => {
export const getGasPrice = (
network: string,
defaultGasPrice: number
): PromiseAction<BigNumber> => async (dispatch: Dispatch): Promise<BigNumber> => {
try {
const gasPrice = await dispatch(Web3Actions.getCurrentGasPrice(network));
return gasPrice === '0' ? new BigNumber(defaultGasPrice) : new BigNumber(gasPrice);
@ -60,7 +66,12 @@ export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAc
};
const estimateProxy: Array<Promise<string>> = [];
export const estimateGasLimit = (network: string, data: string, value: string, gasPrice: string): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
export const estimateGasLimit = (
network: string,
data: string,
value: string,
gasPrice: string
): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
// Since this method could be called multiple times in short period of time
// check for pending calls in proxy and if there more than two (first is current running and the second is waiting for result of first)
// TODO: should reject second call immediately?
@ -69,12 +80,14 @@ export const estimateGasLimit = (network: string, data: string, value: string, g
await estimateProxy[0];
}
const call = dispatch(Web3Actions.estimateGasLimit(network, {
to: '',
data,
value,
gasPrice,
}));
const call = dispatch(
Web3Actions.estimateGasLimit(network, {
to: '',
data,
value,
gasPrice,
})
);
// add current call to proxy
estimateProxy.push(call);
// wait for result
@ -85,8 +98,13 @@ export const estimateGasLimit = (network: string, data: string, value: string, g
return result;
};
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const accounts: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.descriptor); // eslint-disable-line no-unused-vars
export const subscribe = (network: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const accounts: Array<string> = getState()
.accounts.filter(a => a.network === network)
.map(a => a.descriptor); // eslint-disable-line no-unused-vars
const response = await TrezorConnect.blockchainSubscribe({
accounts,
coin: network,
@ -96,7 +114,10 @@ export const subscribe = (network: string): PromiseAction<void> => async (dispat
await dispatch(Web3Actions.initWeb3(network));
};
export const onBlockMined = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onBlockMined = (network: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
// TODO: handle rollback,
// check latest saved transaction blockhash against blockhheight
@ -131,7 +152,9 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (dis
}
};
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onNotification = (
payload: $ElementType<BlockchainNotification, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { notification } = payload;
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
if (!account) return;
@ -148,6 +171,8 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
}
};
export const onError = (network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
export const onError = (network: string): PromiseAction<void> => async (
dispatch: Dispatch
): Promise<void> => {
dispatch(Web3Actions.disconnect(network));
};

@ -5,14 +5,7 @@ import EthereumjsUtil from 'ethereumjs-util';
import * as DISCOVERY from 'actions/constants/discovery';
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
import type {
PromiseAction,
Dispatch,
GetState,
TrezorDevice,
Network,
Account,
} from 'flowtype';
import type { PromiseAction, Dispatch, GetState, TrezorDevice, Network, Account } from 'flowtype';
import type { Discovery } from 'reducers/DiscoveryReducer';
export type DiscoveryStartAction = {
@ -28,7 +21,10 @@ export type DiscoveryStartAction = {
// first iteration
// generate public key for this account
// start discovery process
export const begin = (device: TrezorDevice, network: Network): PromiseAction<DiscoveryStartAction> => async (): Promise<DiscoveryStartAction> => {
export const begin = (
device: TrezorDevice,
network: Network
): PromiseAction<DiscoveryStartAction> => async (): Promise<DiscoveryStartAction> => {
// get xpub from TREZOR
const response = await TrezorConnect.getPublicKey({
device: {
@ -61,18 +57,26 @@ export const begin = (device: TrezorDevice, network: Network): PromiseAction<Dis
};
};
export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): PromiseAction<Account> => async (dispatch: Dispatch, getState: GetState): Promise<Account> => {
export const discoverAccount = (
device: TrezorDevice,
discoveryProcess: Discovery
): PromiseAction<Account> => async (dispatch: Dispatch, getState: GetState): Promise<Account> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
if (!network) throw new Error('Discovery network not found');
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 publicAddress: string = EthereumjsUtil.publicToAddress(
derivedKey.publicKey,
true
).toString('hex');
const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress);
// TODO: check if address was created before
const account = await dispatch(BlockchainActions.discoverAccount(device, ethAddress, network.shortcut));
const account = await dispatch(
BlockchainActions.discoverAccount(device, ethAddress, network.shortcut)
);
// const accountIsEmpty = account.transactions <= 0 && account.nonce <= 0 && account.balance === '0';
const empty = account.nonce <= 0 && account.balance === '0';

@ -36,9 +36,12 @@ const actions = [
];
/*
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
// ignore not listed actions
if (actions.indexOf(action.type) < 0) return;
@ -68,14 +71,26 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
let shouldUpdate: boolean = false;
// check if "selectedAccount" reducer changed
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
account: ['balance', 'nonce', 'tokens'],
});
if (shouldUpdate && currentState.sendFormEthereum.currency !== currentState.sendFormEthereum.networkSymbol) {
shouldUpdate = reducerUtils.observeChanges(
prevState.selectedAccount,
currentState.selectedAccount,
{
account: ['balance', 'nonce', 'tokens'],
}
);
if (
shouldUpdate &&
currentState.sendFormEthereum.currency !== currentState.sendFormEthereum.networkSymbol
) {
// make sure that this token is added into account
const { account, tokens } = getState().selectedAccount;
if (!account) return;
const token = reducerUtils.findToken(tokens, account.descriptor, currentState.sendFormEthereum.currency, account.deviceState);
const token = reducerUtils.findToken(
tokens,
account.descriptor,
currentState.sendFormEthereum.currency,
account.deviceState
);
if (!token) {
// token not found, re-init form
dispatch(init());
@ -85,7 +100,10 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
// check if "sendFormEthereum" reducer changed
if (!shouldUpdate) {
shouldUpdate = reducerUtils.observeChanges(prevState.sendFormEthereum, currentState.sendFormEthereum);
shouldUpdate = reducerUtils.observeChanges(
prevState.sendFormEthereum,
currentState.sendFormEthereum
);
}
if (shouldUpdate) {
@ -99,15 +117,15 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
};
/*
* Called from "observe" action
* Initialize "sendFormEthereum" reducer data
* Get data either from session storage or "selectedAccount" reducer
*/
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const {
account,
network,
} = getState().selectedAccount;
* Called from "observe" action
* Initialize "sendFormEthereum" reducer data
* Get data either from session storage or "selectedAccount" reducer
*/
export const init = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { account, network } = getState().selectedAccount;
if (!account || !network) return;
@ -122,10 +140,15 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
return;
}
const gasPrice: BigNumber = await dispatch(BlockchainActions.getGasPrice(network.shortcut, network.defaultGasPrice));
const gasPrice: BigNumber = await dispatch(
BlockchainActions.getGasPrice(network.shortcut, network.defaultGasPrice)
);
const gasLimit = network.defaultGasLimit.toString();
const feeLevels = ValidationActions.getFeeLevels(network.symbol, gasPrice, gasLimit);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
initialState.selectedFeeLevel
);
dispatch({
type: SEND.INIT,
@ -145,17 +168,20 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
};
/*
* Called from UI from "advanced" button
*/
* Called from UI from "advanced" button
*/
export const toggleAdvanced = (): Action => ({
type: SEND.TOGGLE_ADVANCED,
networkType: 'ethereum',
});
/*
* Called from UI from "clear" button
*/
export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
* Called from UI from "clear" button
*/
export const onClear = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { network } = getState().selectedAccount;
const { advanced } = getState().sendFormEthereum;
@ -164,10 +190,15 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
// clear transaction draft from session storage
dispatch(SessionStorageActions.clear());
const gasPrice: BigNumber = await dispatch(BlockchainActions.getGasPrice(network.shortcut, network.defaultGasPrice));
const gasPrice: BigNumber = await dispatch(
BlockchainActions.getGasPrice(network.shortcut, network.defaultGasPrice)
);
const gasLimit = network.defaultGasLimit.toString();
const feeLevels = ValidationActions.getFeeLevels(network.symbol, gasPrice, gasLimit);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
initialState.selectedFeeLevel
);
dispatch({
type: SEND.CLEAR,
@ -188,9 +219,12 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
};
/*
* Called from UI on "address" field change
*/
export const onAddressChange = (address: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "address" field change
*/
export const onAddressChange = (address: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().sendFormEthereum;
dispatch({
type: SEND.CHANGE,
@ -205,9 +239,12 @@ export const onAddressChange = (address: string): ThunkAction => (dispatch: Disp
};
/*
* Called from UI on "amount" field change
*/
export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "amount" field change
*/
export const onAmountChange = (amount: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state = getState().sendFormEthereum;
dispatch({
type: SEND.CHANGE,
@ -223,22 +260,32 @@ export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispat
};
/*
* Called from UI on "currency" selection change
*/
export const onCurrencyChange = (currency: { value: string, label: string }): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const {
account,
network,
} = getState().selectedAccount;
* Called from UI on "currency" selection change
*/
export const onCurrencyChange = (currency: { value: string, label: string }): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { account, network } = getState().selectedAccount;
if (!account || !network) return;
const state = getState().sendFormEthereum;
const isToken = currency.value !== state.networkSymbol;
const gasLimit = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString();
const feeLevels = ValidationActions.getFeeLevels(network.symbol, state.recommendedGasPrice, gasLimit, state.selectedFeeLevel);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
const gasLimit = isToken
? network.defaultGasLimitTokens.toString()
: network.defaultGasLimit.toString();
const feeLevels = ValidationActions.getFeeLevels(
network.symbol,
state.recommendedGasPrice,
gasLimit,
state.selectedFeeLevel
);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
state.selectedFeeLevel
);
dispatch({
type: SEND.CHANGE,
@ -254,8 +301,8 @@ export const onCurrencyChange = (currency: { value: string, label: string }): Th
};
/*
* Called from UI from "set max" button
*/
* Called from UI from "set max" button
*/
export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const state = getState().sendFormEthereum;
dispatch({
@ -271,9 +318,12 @@ export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
};
/*
* Called from UI on "fee" selection change
*/
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "fee" selection change
*/
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state = getState().sendFormEthereum;
const isCustom = feeLevel.value === 'Custom';
@ -292,7 +342,9 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch:
} else {
// corner case: gas limit was changed by user OR by "estimateGasPrice" action
// leave gasLimit as it is
newGasLimit = state.touched.gasLimit ? state.gasLimit : network.defaultGasLimit.toString();
newGasLimit = state.touched.gasLimit
? state.gasLimit
: network.defaultGasLimit.toString();
}
newGasPrice = feeLevel.gasPrice;
}
@ -311,18 +363,26 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch:
};
/*
* Called from UI from "update recommended fees" button
*/
export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const {
account,
network,
} = getState().selectedAccount;
* Called from UI from "update recommended fees" button
*/
export const updateFeeLevels = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { account, network } = getState().selectedAccount;
if (!account || !network) return;
const state: State = getState().sendFormEthereum;
const feeLevels = ValidationActions.getFeeLevels(network.symbol, state.recommendedGasPrice, state.gasLimit, state.selectedFeeLevel);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
const feeLevels = ValidationActions.getFeeLevels(
network.symbol,
state.recommendedGasPrice,
state.gasLimit,
state.selectedFeeLevel
);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
state.selectedFeeLevel
);
dispatch({
type: SEND.CHANGE,
@ -338,13 +398,17 @@ export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState:
};
/*
* Called from UI on "gas price" field change
*/
export const onGasPriceChange = (gasPrice: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "gas price" field change
*/
export const onGasPriceChange = (gasPrice: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().sendFormEthereum;
// switch to custom fee level
let newSelectedFeeLevel = state.selectedFeeLevel;
if (state.selectedFeeLevel.value !== 'Custom') newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
if (state.selectedFeeLevel.value !== 'Custom')
newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
dispatch({
type: SEND.CHANGE,
@ -360,16 +424,27 @@ export const onGasPriceChange = (gasPrice: string): ThunkAction => (dispatch: Di
};
/*
* Called from UI on "data" field change
* OR from "estimateGasPrice" action
*/
export const onGasLimitChange = (gasLimit: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "data" field change
* OR from "estimateGasPrice" action
*/
export const onGasLimitChange = (gasLimit: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { network } = getState().selectedAccount;
if (!network) return;
const state: State = getState().sendFormEthereum;
// recalculate feeLevels with recommended gasPrice
const feeLevels = ValidationActions.getFeeLevels(network.symbol, state.recommendedGasPrice, gasLimit, state.selectedFeeLevel);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
const feeLevels = ValidationActions.getFeeLevels(
network.symbol,
state.recommendedGasPrice,
gasLimit,
state.selectedFeeLevel
);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
state.selectedFeeLevel
);
dispatch({
type: SEND.CHANGE,
@ -387,9 +462,12 @@ export const onGasLimitChange = (gasLimit: string): ThunkAction => (dispatch: Di
};
/*
* Called from UI on "nonce" field change
*/
export const onNonceChange = (nonce: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "nonce" field change
*/
export const onNonceChange = (nonce: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().sendFormEthereum;
dispatch({
type: SEND.CHANGE,
@ -404,9 +482,12 @@ export const onNonceChange = (nonce: string): ThunkAction => (dispatch: Dispatch
};
/*
* Called from UI on "data" field change
*/
export const onDataChange = (data: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "data" field change
*/
export const onDataChange = (data: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().sendFormEthereum;
dispatch({
type: SEND.CHANGE,
@ -423,13 +504,18 @@ export const onDataChange = (data: string): ThunkAction => (dispatch: Dispatch,
dispatch(estimateGasPrice());
};
export const setDefaultGasLimit = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const setDefaultGasLimit = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().sendFormEthereum;
const { network } = getState().selectedAccount;
if (!network) return;
const isToken = state.currency !== state.networkSymbol;
const gasLimit = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString();
const gasLimit = isToken
? network.defaultGasLimitTokens.toString()
: network.defaultGasLimit.toString();
dispatch({
type: SEND.CHANGE,
@ -445,11 +531,14 @@ export const setDefaultGasLimit = (): ThunkAction => (dispatch: Dispatch, getSta
};
/*
* Internal method
* Called from "onDataChange" action
* try to asynchronously download data from backend
*/
const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
* Internal method
* Called from "onDataChange" action
* try to asynchronously download data from backend
*/
const estimateGasPrice = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const state: State = getState().sendFormEthereum;
const { network } = getState().selectedAccount;
if (!network) {
@ -461,7 +550,11 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
const requestedData = state.data;
if (!ethUtils.isHex(requestedData)) {
// stop "calculatingGasLimit" process
dispatch(onGasLimitChange(requestedData.length > 0 ? state.gasLimit : network.defaultGasLimit.toString()));
dispatch(
onGasLimitChange(
requestedData.length > 0 ? state.gasLimit : network.defaultGasLimit.toString()
)
);
return;
}
@ -471,7 +564,14 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
return;
}
const gasLimit = await dispatch(BlockchainActions.estimateGasLimit(network.shortcut, state.data, state.amount, state.gasPrice));
const gasLimit = await dispatch(
BlockchainActions.estimateGasLimit(
network.shortcut,
state.data,
state.amount,
state.gasPrice
)
);
// double check "data" field
// possible race condition when data changed before backend respond
@ -481,14 +581,13 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
};
/*
* Called from UI from "send" button
*/
export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const {
account,
network,
pending,
} = getState().selectedAccount;
* Called from UI from "send" button
*/
export const onSend = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { account, network, pending } = getState().selectedAccount;
if (!account || account.networkType !== 'ethereum' || !network) return;
@ -498,17 +597,26 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
const pendingNonce: number = reducerUtils.getPendingSequence(pending);
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
const txData = await dispatch(prepareEthereumTx({
network: network.shortcut,
token: isToken ? reducerUtils.findToken(getState().tokens, account.descriptor, currentState.currency, account.deviceState) : null,
from: account.descriptor,
to: currentState.address,
amount: currentState.amount,
data: currentState.data,
gasLimit: currentState.gasLimit,
gasPrice: currentState.gasPrice,
nonce,
}));
const txData = await dispatch(
prepareEthereumTx({
network: network.shortcut,
token: isToken
? reducerUtils.findToken(
getState().tokens,
account.descriptor,
currentState.currency,
account.deviceState
)
: null,
from: account.descriptor,
to: currentState.address,
amount: currentState.amount,
data: currentState.data,
gasLimit: currentState.gasLimit,
gasPrice: currentState.gasPrice,
nonce,
})
);
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
if (!selected) return;
@ -586,22 +694,28 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
total: currentState.total,
sequence: nonce,
tokens: isToken ? [{
name: currentState.currency,
shortcut: currentState.currency,
value: currentState.amount,
}] : undefined,
tokens: isToken
? [
{
name: currentState.currency,
shortcut: currentState.currency,
value: currentState.amount,
},
]
: undefined,
blockHeight: 0,
blockHash: undefined,
timestamp: undefined,
};
dispatch(BlockchainActions.onNotification({
// $FlowIssue: missing coinInfo declaration
coin: {},
notification: blockchainNotification,
}));
dispatch(
BlockchainActions.onNotification({
// $FlowIssue: missing coinInfo declaration
coin: {},
notification: blockchainNotification,
})
);
// workaround end
// clear session storage
@ -615,7 +729,11 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
payload: {
type: 'success',
title: 'Transaction success',
message: <Link href={`${network.explorer.tx}${txid}`} isGreen>See transaction detail</Link>,
message: (
<Link href={`${network.explorer.tx}${txid}`} isGreen>
See transaction detail
</Link>
),
cancelable: true,
actions: [],
},

@ -7,30 +7,33 @@ import { findDevice, getPendingAmount, findToken } from 'reducers/utils';
import * as SEND from 'actions/constants/send';
import * as ethUtils from 'utils/ethUtils';
import type {
Dispatch,
GetState,
PayloadAction,
} from 'flowtype';
import type { Dispatch, GetState, PayloadAction } from 'flowtype';
import type { State, FeeLevel } from 'reducers/SendFormEthereumReducer';
// general regular expressions
const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$');
const UPPERCASE_RE = new RegExp('^(.*[A-Z].*)$');
const ABS_RE = new RegExp('^[0-9]+$');
const ETH_18_RE = new RegExp('^(0|0\\.([0-9]{0,18})?|[1-9][0-9]*\\.?([0-9]{0,18})?|\\.[0-9]{0,18})$');
const ETH_18_RE = new RegExp(
'^(0|0\\.([0-9]{0,18})?|[1-9][0-9]*\\.?([0-9]{0,18})?|\\.[0-9]{0,18})$'
);
const dynamicRegexp = (decimals: number): RegExp => {
if (decimals > 0) {
return new RegExp(`^(0|0\\.([0-9]{0,${decimals}})?|[1-9][0-9]*\\.?([0-9]{0,${decimals}})?|\\.[0-9]{1,${decimals}})$`);
return new RegExp(
`^(0|0\\.([0-9]{0,${decimals}})?|[1-9][0-9]*\\.?([0-9]{0,${decimals}})?|\\.[0-9]{1,${decimals}})$`
);
}
return ABS_RE;
};
/*
* Called from SendFormActions.observe
* Reaction for WEB3.GAS_PRICE_UPDATED action
*/
export const onGasPriceUpdated = (network: string, gasPrice: string): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
* Called from SendFormActions.observe
* Reaction for WEB3.GAS_PRICE_UPDATED action
*/
export const onGasPriceUpdated = (network: string, gasPrice: string): PayloadAction<void> => (
dispatch: Dispatch,
getState: GetState
): void => {
// testing random data
// function getRandomInt(min, max) {
// return Math.floor(Math.random() * (max - min + 1)) + min;
@ -77,9 +80,12 @@ export const onGasPriceUpdated = (network: string, gasPrice: string): PayloadAct
};
/*
* Recalculate amount, total and fees
*/
export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Recalculate amount, total and fees
*/
export const validation = (): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
// clone deep nested object
// to avoid overrides across state history
let state: State = JSON.parse(JSON.stringify(getState().sendFormEthereum));
@ -99,12 +105,11 @@ export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getSt
return state;
};
export const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
const {
account,
tokens,
pending,
} = getState().selectedAccount;
export const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const { account, tokens, pending } = getState().selectedAccount;
if (!account) return $state;
const state = { ...$state };
@ -113,7 +118,12 @@ export const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
if (state.setMax) {
const pendingAmount = getPendingAmount(pending, state.currency, isToken);
if (isToken) {
const token = findToken(tokens, account.descriptor, state.currency, account.deviceState);
const token = findToken(
tokens,
account.descriptor,
state.currency,
account.deviceState
);
if (token) {
state.amount = new BigNumber(token.balance).minus(pendingAmount).toFixed();
}
@ -140,8 +150,8 @@ export const updateCustomFeeLabel = ($state: State): PayloadAction<State> => ():
};
/*
* Address value validation
*/
* Address value validation
*/
export const addressValidation = ($state: State): PayloadAction<State> => (): State => {
const state = { ...$state };
if (!state.touched.address) return state;
@ -159,36 +169,52 @@ export const addressValidation = ($state: State): PayloadAction<State> => (): St
};
/*
* Address label assignation
*/
export const addressLabel = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Address label assignation
*/
export const addressLabel = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.address || state.errors.address) return state;
const {
account,
network,
} = getState().selectedAccount;
const { account, network } = getState().selectedAccount;
if (!account || !network) return state;
const { address } = state;
const savedAccounts = getState().accounts.filter(a => a.descriptor.toLowerCase() === address.toLowerCase());
const savedAccounts = getState().accounts.filter(
a => a.descriptor.toLowerCase() === address.toLowerCase()
);
if (savedAccounts.length > 0) {
// check if found account belongs to this network
const currentNetworkAccount = savedAccounts.find(a => a.network === network.shortcut);
if (currentNetworkAccount) {
const device = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState);
const device = findDevice(
getState().devices,
currentNetworkAccount.deviceID,
currentNetworkAccount.deviceState
);
if (device) {
state.infos.address = `${device.instanceLabel} Account #${(currentNetworkAccount.index + 1)}`;
state.infos.address = `${
device.instanceLabel
} Account #${currentNetworkAccount.index + 1}`;
}
} else {
// corner-case: the same derivation path is used on different networks
const otherNetworkAccount = savedAccounts[0];
const device = findDevice(getState().devices, otherNetworkAccount.deviceID, otherNetworkAccount.deviceState);
const device = findDevice(
getState().devices,
otherNetworkAccount.deviceID,
otherNetworkAccount.deviceState
);
const { networks } = getState().localStorage.config;
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
if (device && otherNetwork) {
state.warnings.address = `Looks like it's ${device.instanceLabel} Account #${(otherNetworkAccount.index + 1)} address of ${otherNetwork.name} network`;
state.warnings.address = `Looks like it's ${
device.instanceLabel
} Account #${otherNetworkAccount.index + 1} address of ${
otherNetwork.name
} network`;
}
}
}
@ -197,17 +223,16 @@ export const addressLabel = ($state: State): PayloadAction<State> => (dispatch:
};
/*
* Amount value validation
*/
export const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Amount value validation
*/
export const amountValidation = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.amount) return state;
const {
account,
tokens,
pending,
} = getState().selectedAccount;
const { account, tokens, pending } = getState().selectedAccount;
if (!account) return state;
const { amount } = state;
@ -220,7 +245,12 @@ export const amountValidation = ($state: State): PayloadAction<State> => (dispat
const pendingAmount: BigNumber = getPendingAmount(pending, state.currency, isToken);
if (isToken) {
const token = findToken(tokens, account.descriptor, state.currency, account.deviceState);
const token = findToken(
tokens,
account.descriptor,
state.currency,
account.deviceState
);
if (!token) return state;
const decimalRegExp = dynamicRegexp(parseInt(token.decimals, 0));
@ -228,14 +258,22 @@ export const amountValidation = ($state: State): PayloadAction<State> => (dispat
state.errors.amount = `Maximum ${token.decimals} decimals allowed`;
} else if (new BigNumber(state.total).isGreaterThan(account.balance)) {
state.errors.amount = `Not enough ${state.networkSymbol} to cover transaction fee`;
} else if (new BigNumber(state.amount).isGreaterThan(new BigNumber(token.balance).minus(pendingAmount))) {
} else if (
new BigNumber(state.amount).isGreaterThan(
new BigNumber(token.balance).minus(pendingAmount)
)
) {
state.errors.amount = 'Not enough funds';
} else if (new BigNumber(state.amount).isLessThanOrEqualTo('0')) {
state.errors.amount = 'Amount is too low';
}
} else if (!state.amount.match(ETH_18_RE)) {
state.errors.amount = 'Maximum 18 decimals allowed';
} else if (new BigNumber(state.total).isGreaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
} else if (
new BigNumber(state.total).isGreaterThan(
new BigNumber(account.balance).minus(pendingAmount)
)
) {
state.errors.amount = 'Not enough funds';
}
}
@ -243,15 +281,16 @@ export const amountValidation = ($state: State): PayloadAction<State> => (dispat
};
/*
* Gas limit value validation
*/
export const gasLimitValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Gas limit value validation
*/
export const gasLimitValidation = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.gasLimit) return state;
const {
network,
} = getState().selectedAccount;
const { network } = getState().selectedAccount;
if (!network) return state;
const { gasLimit } = state;
@ -263,7 +302,13 @@ export const gasLimitValidation = ($state: State): PayloadAction<State> => (disp
const gl: BigNumber = new BigNumber(gasLimit);
if (gl.isLessThan(1)) {
state.errors.gasLimit = 'Gas limit is too low';
} else if (gl.isLessThan(state.currency !== state.networkSymbol ? network.defaultGasLimitTokens : network.defaultGasLimit)) {
} else if (
gl.isLessThan(
state.currency !== state.networkSymbol
? network.defaultGasLimitTokens
: network.defaultGasLimit
)
) {
state.warnings.gasLimit = 'Gas limit is below recommended';
}
}
@ -271,8 +316,8 @@ export const gasLimitValidation = ($state: State): PayloadAction<State> => (disp
};
/*
* Gas price value validation
*/
* Gas price value validation
*/
export const gasPriceValidation = ($state: State): PayloadAction<State> => (): State => {
const state = { ...$state };
if (!state.touched.gasPrice) return state;
@ -294,15 +339,16 @@ export const gasPriceValidation = ($state: State): PayloadAction<State> => (): S
};
/*
* Nonce value validation
*/
export const nonceValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Nonce value validation
*/
export const nonceValidation = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.nonce) return state;
const {
account,
} = getState().selectedAccount;
const { account } = getState().selectedAccount;
if (!account || account.networkType !== 'ethereum') return state;
const { nonce } = state;
@ -322,8 +368,8 @@ export const nonceValidation = ($state: State): PayloadAction<State> => (dispatc
};
/*
* Gas price value validation
*/
* Gas price value validation
*/
export const dataValidation = ($state: State): PayloadAction<State> => (): State => {
const state = { ...$state };
if (!state.touched.data || state.data.length === 0) return state;
@ -334,12 +380,16 @@ export const dataValidation = ($state: State): PayloadAction<State> => (): State
};
/*
* UTILITIES
*/
* UTILITIES
*/
export const calculateFee = (gasPrice: string, gasLimit: string): string => {
try {
return EthereumjsUnits.convert(new BigNumber(gasPrice).times(gasLimit).toFixed(), 'gwei', 'ether');
return EthereumjsUnits.convert(
new BigNumber(gasPrice).times(gasLimit).toFixed(),
'gwei',
'ether'
);
} catch (error) {
return '0';
}
@ -358,7 +408,11 @@ export const calculateTotal = (amount: string, gasPrice: string, gasLimit: strin
}
};
export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimit: string): string => {
export const calculateMaxAmount = (
balance: BigNumber,
gasPrice: string,
gasLimit: string
): string => {
try {
// TODO - minus pendings
const fee = calculateFee(gasPrice, gasLimit);
@ -370,22 +424,30 @@ export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimi
}
};
export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string, selected?: FeeLevel): Array<FeeLevel> => {
export const getFeeLevels = (
symbol: string,
gasPrice: BigNumber | string,
gasLimit: string,
selected?: FeeLevel
): Array<FeeLevel> => {
const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice;
const quarter: BigNumber = price.dividedBy(4);
const high: string = price.plus(quarter.times(2)).toFixed();
const low: string = price.minus(quarter.times(2)).toFixed();
const customLevel: FeeLevel = selected && selected.value === 'Custom' ? {
value: 'Custom',
gasPrice: selected.gasPrice,
// label: `${ calculateFee(gasPrice, gasLimit) } ${ symbol }`
label: `${calculateFee(selected.gasPrice, gasLimit)} ${symbol}`,
} : {
value: 'Custom',
gasPrice: low,
label: '',
};
const customLevel: FeeLevel =
selected && selected.value === 'Custom'
? {
value: 'Custom',
gasPrice: selected.gasPrice,
// label: `${ calculateFee(gasPrice, gasLimit) } ${ symbol }`
label: `${calculateFee(selected.gasPrice, gasLimit)} ${symbol}`,
}
: {
value: 'Custom',
gasPrice: low,
label: '',
};
return [
{

@ -17,8 +17,13 @@ import type {
BlockchainFeeLevel,
} from 'flowtype';
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const accounts: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.descriptor);
export const subscribe = (network: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const accounts: Array<string> = getState()
.accounts.filter(a => a.network === network)
.map(a => a.descriptor);
await TrezorConnect.blockchainSubscribe({
accounts,
coin: network,
@ -28,7 +33,10 @@ export const subscribe = (network: string): PromiseAction<void> => async (dispat
// Get current known fee
// Use default values from appConfig.json if it wasn't downloaded from blockchain yet
// update them later, after onBlockMined event
export const getFeeLevels = (network: Network): PayloadAction<Array<BlockchainFeeLevel>> => (dispatch: Dispatch, getState: GetState): Array<BlockchainFeeLevel> => {
export const getFeeLevels = (network: Network): PayloadAction<Array<BlockchainFeeLevel>> => (
dispatch: Dispatch,
getState: GetState
): Array<BlockchainFeeLevel> => {
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
if (!blockchain || blockchain.feeLevels.length < 1) {
return network.fee.levels.map(level => ({
@ -39,7 +47,10 @@ export const getFeeLevels = (network: Network): PayloadAction<Array<BlockchainFe
return blockchain.feeLevels;
};
export const onBlockMined = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onBlockMined = (network: string): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const blockchain = getState().blockchain.find(b => b.shortcut === network);
if (!blockchain) return; // flowtype fallback
@ -69,9 +80,7 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (dis
// level: 'transactions',
// coin: network,
// });
// if (!response.success) return;
// response.payload.forEach((a, i) => {
// if (a.transactions.length > 0) {
// console.warn('APDEJTED!', a, i);
@ -87,7 +96,9 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (dis
}
};
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const onNotification = (
payload: $ElementType<BlockchainNotification, 'payload'>
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { notification } = payload;
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
if (!account) return;
@ -103,7 +114,10 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
network: account.network,
amount: toDecimalAmount(notification.amount, network.decimals),
total: notification.type === 'send' ? toDecimalAmount(notification.total, network.decimals) : toDecimalAmount(notification.amount, network.decimals),
total:
notification.type === 'send'
? toDecimalAmount(notification.total, network.decimals)
: toDecimalAmount(notification.amount, network.decimals),
fee: toDecimalAmount(notification.fee, network.decimals),
},
});
@ -126,13 +140,18 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
});
if (!updatedAccount.success) return;
dispatch(AccountsActions.update({
networkType: 'ripple',
...account,
balance: toDecimalAmount(updatedAccount.payload.balance, network.decimals),
availableBalance: toDecimalAmount(updatedAccount.payload.availableBalance, network.decimals),
block: updatedAccount.payload.block,
sequence: updatedAccount.payload.sequence,
reserve: '0',
}));
dispatch(
AccountsActions.update({
networkType: 'ripple',
...account,
balance: toDecimalAmount(updatedAccount.payload.balance, network.decimals),
availableBalance: toDecimalAmount(
updatedAccount.payload.availableBalance,
network.decimals
),
block: updatedAccount.payload.block,
sequence: updatedAccount.payload.sequence,
reserve: '0',
})
);
};

@ -4,14 +4,7 @@ import TrezorConnect from 'trezor-connect';
import * as DISCOVERY from 'actions/constants/discovery';
import { toDecimalAmount } from 'utils/formatUtils';
import type {
PromiseAction,
GetState,
Dispatch,
TrezorDevice,
Network,
Account,
} from 'flowtype';
import type { PromiseAction, GetState, Dispatch, TrezorDevice, Network, Account } from 'flowtype';
import type { Discovery } from 'reducers/DiscoveryReducer';
export type DiscoveryStartAction = {
@ -21,14 +14,20 @@ export type DiscoveryStartAction = {
device: TrezorDevice,
};
export const begin = (device: TrezorDevice, network: Network): PromiseAction<DiscoveryStartAction> => async (): Promise<DiscoveryStartAction> => ({
export const begin = (
device: TrezorDevice,
network: Network
): PromiseAction<DiscoveryStartAction> => async (): Promise<DiscoveryStartAction> => ({
type: DISCOVERY.START,
networkType: 'ripple',
network,
device,
});
export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): PromiseAction<Account> => async (dispatch: Dispatch, getState: GetState): Promise<Account> => {
export const discoverAccount = (
device: TrezorDevice,
discoveryProcess: Discovery
): PromiseAction<Account> => async (dispatch: Dispatch, getState: GetState): Promise<Account> => {
const { config } = getState().localStorage;
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
if (!network) throw new Error('Discovery network not found');

@ -23,9 +23,12 @@ import * as BlockchainActions from './BlockchainActions';
import * as ValidationActions from './SendFormValidationActions';
/*
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from WalletService
*/
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const currentState = getState();
// if action type is SEND.VALIDATION which is called as result of this process
@ -50,13 +53,20 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
let shouldUpdate: boolean = false;
// check if "selectedAccount" reducer changed
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
account: ['balance', 'sequence'],
});
shouldUpdate = reducerUtils.observeChanges(
prevState.selectedAccount,
currentState.selectedAccount,
{
account: ['balance', 'sequence'],
}
);
// check if "sendForm" reducer changed
if (!shouldUpdate) {
shouldUpdate = reducerUtils.observeChanges(prevState.sendFormRipple, currentState.sendFormRipple);
shouldUpdate = reducerUtils.observeChanges(
prevState.sendFormRipple,
currentState.sendFormRipple
);
}
if (shouldUpdate) {
@ -70,15 +80,15 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
};
/*
* Called from "observe" action
* Initialize "sendFormRipple" reducer data
* Get data either from session storage or "selectedAccount" reducer
*/
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const {
account,
network,
} = getState().selectedAccount;
* Called from "observe" action
* Initialize "sendFormRipple" reducer data
* Get data either from session storage or "selectedAccount" reducer
*/
export const init = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { account, network } = getState().selectedAccount;
if (!account || account.networkType !== 'ripple' || !network) return;
@ -94,7 +104,10 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
initialState.selectedFeeLevel
);
dispatch({
type: SEND.INIT,
@ -112,17 +125,20 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
};
/*
* Called from UI from "advanced" button
*/
* Called from UI from "advanced" button
*/
export const toggleAdvanced = (): Action => ({
type: SEND.TOGGLE_ADVANCED,
networkType: 'ripple',
});
/*
* Called from UI from "clear" button
*/
export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
* Called from UI from "clear" button
*/
export const onClear = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { network } = getState().selectedAccount;
const { advanced } = getState().sendFormRipple;
@ -133,7 +149,10 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
initialState.selectedFeeLevel
);
dispatch({
type: SEND.CLEAR,
@ -152,9 +171,12 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
};
/*
* Called from UI on "address" field change
*/
export const onAddressChange = (address: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "address" field change
*/
export const onAddressChange = (address: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().sendFormRipple;
dispatch({
type: SEND.CHANGE,
@ -169,9 +191,12 @@ export const onAddressChange = (address: string): ThunkAction => (dispatch: Disp
};
/*
* Called from UI on "amount" field change
*/
export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "amount" field change
*/
export const onAmountChange = (amount: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state = getState().sendFormRipple;
dispatch({
type: SEND.CHANGE,
@ -187,8 +212,8 @@ export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispat
};
/*
* Called from UI from "set max" button
*/
* Called from UI from "set max" button
*/
export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const state = getState().sendFormRipple;
dispatch({
@ -204,9 +229,12 @@ export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
};
/*
* Called from UI on "fee" selection change
*/
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "fee" selection change
*/
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state = getState().sendFormRipple;
const isCustom = feeLevel.value === 'Custom';
@ -225,19 +253,24 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch:
};
/*
* Called from UI from "update recommended fees" button
*/
export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const {
account,
network,
} = getState().selectedAccount;
* Called from UI from "update recommended fees" button
*/
export const updateFeeLevels = (): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { account, network } = getState().selectedAccount;
if (!account || !network) return;
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
const state: State = getState().sendFormRipple;
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels, state.selectedFeeLevel));
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
const feeLevels = dispatch(
ValidationActions.getFeeLevels(blockchainFeeLevels, state.selectedFeeLevel)
);
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
feeLevels,
state.selectedFeeLevel
);
dispatch({
type: SEND.CHANGE,
@ -252,16 +285,20 @@ export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState:
};
/*
* Called from UI on "advanced / fee" field change
*/
export const onFeeChange = (fee: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "advanced / fee" field change
*/
export const onFeeChange = (fee: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const { network } = getState().selectedAccount;
if (!network) return;
const state: State = getState().sendFormRipple;
// switch to custom fee level
let newSelectedFeeLevel = state.selectedFeeLevel;
if (state.selectedFeeLevel.value !== 'Custom') newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
if (state.selectedFeeLevel.value !== 'Custom')
newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
dispatch({
type: SEND.CHANGE,
@ -277,9 +314,12 @@ export const onFeeChange = (fee: string): ThunkAction => (dispatch: Dispatch, ge
};
/*
* Called from UI on "advanced / destination tag" field change
*/
export const onDestinationTagChange = (destinationTag: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
* Called from UI on "advanced / destination tag" field change
*/
export const onDestinationTagChange = (destinationTag: string): ThunkAction => (
dispatch: Dispatch,
getState: GetState
): void => {
const state: State = getState().sendFormRipple;
dispatch({
type: SEND.CHANGE,
@ -294,13 +334,13 @@ export const onDestinationTagChange = (destinationTag: string): ThunkAction => (
};
/*
* Called from UI from "send" button
*/
export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const {
account,
network,
} = getState().selectedAccount;
* Called from UI from "send" button
*/
export const onSend = (): AsyncAction => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { account, network } = getState().selectedAccount;
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
if (!selected) return;

@ -21,10 +21,13 @@ const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?
const XRP_6_RE = new RegExp('^(0|0\\.([0-9]{0,6})?|[1-9][0-9]*\\.?([0-9]{0,6})?|\\.[0-9]{0,6})$');
/*
* Called from SendFormActions.observe
* Reaction for BLOCKCHAIN.FEE_UPDATED action
*/
export const onFeeUpdated = (network: string, feeLevels: Array<BlockchainFeeLevel>): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
* Called from SendFormActions.observe
* Reaction for BLOCKCHAIN.FEE_UPDATED action
*/
export const onFeeUpdated = (
network: string,
feeLevels: Array<BlockchainFeeLevel>
): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
const state = getState().sendFormRipple;
if (network === state.networkSymbol) return;
@ -58,9 +61,12 @@ export const onFeeUpdated = (network: string, feeLevels: Array<BlockchainFeeLeve
};
/*
* Recalculate amount, total and fees
*/
export const validation = (prevState: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Recalculate amount, total and fees
*/
export const validation = (prevState: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
// clone deep nested object
// to avoid overrides across state history
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
@ -81,12 +87,11 @@ export const validation = (prevState: State): PayloadAction<State> => (dispatch:
return state;
};
const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
const {
account,
network,
pending,
} = getState().selectedAccount;
const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const { account, network, pending } = getState().selectedAccount;
if (!account || account.networkType !== 'ripple' || !network) return $state;
const state = { ...$state };
@ -94,7 +99,9 @@ const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatc
if (state.setMax) {
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
const availableBalance = new BigNumber(account.balance).minus(account.reserve).minus(pendingAmount);
const availableBalance = new BigNumber(account.balance)
.minus(account.reserve)
.minus(pendingAmount);
state.amount = calculateMaxAmount(availableBalance, fee);
}
@ -102,7 +109,10 @@ const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatc
return state;
};
const updateCustomFeeLabel = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
const updateCustomFeeLabel = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const { network } = getState().selectedAccount;
if (!network) return $state; // flowtype fallback
@ -118,9 +128,12 @@ const updateCustomFeeLabel = ($state: State): PayloadAction<State> => (dispatch:
};
/*
* Address value validation
*/
const addressValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Address value validation
*/
const addressValidation = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.address) return state;
@ -140,10 +153,13 @@ const addressValidation = ($state: State): PayloadAction<State> => (dispatch: Di
};
/*
* Address balance validation
* Fetch data from trezor-connect and set minimum required amount in reducer
*/
const addressBalanceValidation = ($state: State): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
* Address balance validation
* Fetch data from trezor-connect and set minimum required amount in reducer
*/
const addressBalanceValidation = ($state: State): PromiseAction<void> => async (
dispatch: Dispatch,
getState: GetState
): Promise<void> => {
const { network } = getState().selectedAccount;
if (!network) return;
@ -177,36 +193,52 @@ const addressBalanceValidation = ($state: State): PromiseAction<void> => async (
};
/*
* Address label assignation
*/
const addressLabel = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Address label assignation
*/
const addressLabel = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.address || state.errors.address) return state;
const {
account,
network,
} = getState().selectedAccount;
const { account, network } = getState().selectedAccount;
if (!account || !network) return state;
const { address } = state;
const savedAccounts = getState().accounts.filter(a => a.descriptor.toLowerCase() === address.toLowerCase());
const savedAccounts = getState().accounts.filter(
a => a.descriptor.toLowerCase() === address.toLowerCase()
);
if (savedAccounts.length > 0) {
// check if found account belongs to this network
const currentNetworkAccount = savedAccounts.find(a => a.network === network.shortcut);
if (currentNetworkAccount) {
const device = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState);
const device = findDevice(
getState().devices,
currentNetworkAccount.deviceID,
currentNetworkAccount.deviceState
);
if (device) {
state.infos.address = `${device.instanceLabel} Account #${(currentNetworkAccount.index + 1)}`;
state.infos.address = `${
device.instanceLabel
} Account #${currentNetworkAccount.index + 1}`;
}
} else {
// corner-case: the same derivation path is used on different networks
const otherNetworkAccount = savedAccounts[0];
const device = findDevice(getState().devices, otherNetworkAccount.deviceID, otherNetworkAccount.deviceState);
const device = findDevice(
getState().devices,
otherNetworkAccount.deviceID,
otherNetworkAccount.deviceState
);
const { networks } = getState().localStorage.config;
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
if (device && otherNetwork) {
state.warnings.address = `Looks like it's ${device.instanceLabel} Account #${(otherNetworkAccount.index + 1)} address of ${otherNetwork.name} network`;
state.warnings.address = `Looks like it's ${
device.instanceLabel
} Account #${otherNetworkAccount.index + 1} address of ${
otherNetwork.name
} network`;
}
}
}
@ -215,16 +247,16 @@ const addressLabel = ($state: State): PayloadAction<State> => (dispatch: Dispatc
};
/*
* Amount value validation
*/
const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Amount value validation
*/
const amountValidation = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.amount) return state;
const {
account,
pending,
} = getState().selectedAccount;
const { account, pending } = getState().selectedAccount;
if (!account || account.networkType !== 'ripple') return state;
const { amount } = state;
@ -236,32 +268,44 @@ const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dis
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
if (!state.amount.match(XRP_6_RE)) {
state.errors.amount = 'Maximum 6 decimals allowed';
} else if (new BigNumber(state.total).isGreaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
} else if (
new BigNumber(state.total).isGreaterThan(
new BigNumber(account.balance).minus(pendingAmount)
)
) {
state.errors.amount = 'Not enough funds';
}
}
if (!state.errors.amount && new BigNumber(account.balance).minus(state.total).lt(account.reserve)) {
state.errors.amount = `Not enough funds. Reserved amount for this account is ${account.reserve} ${state.networkSymbol}`;
if (
!state.errors.amount &&
new BigNumber(account.balance).minus(state.total).lt(account.reserve)
) {
state.errors.amount = `Not enough funds. Reserved amount for this account is ${
account.reserve
} ${state.networkSymbol}`;
}
if (!state.errors.amount && new BigNumber(state.amount).lt(state.minAmount)) {
state.errors.amount = `Amount is too low. Minimum amount for creating a new account is ${state.minAmount} ${state.networkSymbol}`;
state.errors.amount = `Amount is too low. Minimum amount for creating a new account is ${
state.minAmount
} ${state.networkSymbol}`;
}
return state;
};
/*
* Fee value validation
*/
export const feeValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
* Fee value validation
*/
export const feeValidation = ($state: State): PayloadAction<State> => (
dispatch: Dispatch,
getState: GetState
): State => {
const state = { ...$state };
if (!state.touched.fee) return state;
const {
network,
} = getState().selectedAccount;
const { network } = getState().selectedAccount;
if (!network) return state;
const { fee } = state;
@ -280,8 +324,8 @@ export const feeValidation = ($state: State): PayloadAction<State> => (dispatch:
return state;
};
/*
* Destination Tag value validation
*/
* Destination Tag value validation
*/
export const destinationTagValidation = ($state: State): PayloadAction<State> => (): State => {
const state = { ...$state };
if (!state.touched.destinationTag) return state;
@ -293,10 +337,9 @@ export const destinationTagValidation = ($state: State): PayloadAction<State> =>
return state;
};
/*
* UTILITIES
*/
* UTILITIES
*/
const calculateTotal = (amount: string, fee: string): string => {
try {
@ -323,7 +366,10 @@ const calculateMaxAmount = (balance: BigNumber, fee: string): string => {
};
// Generate FeeLevel dataset for "fee" select
export const getFeeLevels = (feeLevels: Array<BlockchainFeeLevel>, selected?: FeeLevel): PayloadAction<Array<FeeLevel>> => (dispatch: Dispatch, getState: GetState): Array<FeeLevel> => {
export const getFeeLevels = (
feeLevels: Array<BlockchainFeeLevel>,
selected?: FeeLevel
): PayloadAction<Array<FeeLevel>> => (dispatch: Dispatch, getState: GetState): Array<FeeLevel> => {
const { network } = getState().selectedAccount;
if (!network) return []; // flowtype fallback
@ -335,15 +381,18 @@ export const getFeeLevels = (feeLevels: Array<BlockchainFeeLevel>, selected?: Fe
}));
// add "Custom" level
const customLevel = selected && selected.value === 'Custom' ? {
value: 'Custom',
fee: selected.fee,
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
} : {
value: 'Custom',
fee: '0',
label: '',
};
const customLevel =
selected && selected.value === 'Custom'
? {
value: 'Custom',
fee: selected.fee,
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
}
: {
value: 'Custom',
fee: '0',
label: '',
};
return levels.concat([customLevel]);
};

@ -3,7 +3,6 @@ import styled, { css } from 'styled-components';
import PropTypes from 'prop-types';
import { FADE_IN } from 'config/animations';
const StyledBackdrop = styled.div`
width: 100%;
height: 100%;
@ -11,21 +10,17 @@ const StyledBackdrop = styled.div`
z-index: 100;
left: 0;
top: 0;
background-color: rgba(0,0,0,0.5);
background-color: rgba(0, 0, 0, 0.5);
${props => props.animated && css`
animation: ${FADE_IN} 0.3s;
`};
${props =>
props.animated &&
css`
animation: ${FADE_IN} 0.3s;
`};
`;
const Backdrop = ({
className,
show,
animated,
onClick,
}) => (
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null
);
const Backdrop = ({ className, show, animated, onClick }) =>
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null;
Backdrop.propTypes = {
show: PropTypes.bool,

@ -17,8 +17,8 @@ type Props = {
isWhite?: boolean,
isWebUsb?: boolean,
isTransparent?: boolean,
dataTest?: string
}
dataTest?: string,
};
const Wrapper = styled.button`
padding: ${props => (props.icon ? '4px 24px 4px 15px' : '11px 24px')};
@ -43,97 +43,105 @@ const Wrapper = styled.button`
box-shadow: 0 0px 6px 0 ${colors.INPUT_FOCUSED_SHADOW};
}
${props => props.isDisabled && css`
pointer-events: none;
color: ${colors.TEXT_SECONDARY};
background: ${colors.GRAY_LIGHT};
`}
${props => props.isWhite && css`
background: ${colors.WHITE};
color: ${colors.TEXT_SECONDARY};
border: 1px solid ${colors.DIVIDER};
&:focus {
border-color: ${colors.INPUT_FOCUSED_BORDER};
}
&:hover {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
&:active {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
`}
${props => props.isTransparent && css`
background: transparent;
border: 0px;
color: ${colors.TEXT_SECONDARY};
&:focus {
color: ${colors.TEXT_PRIMARY};
box-shadow: none;
}
&:hover,
&:active {
color: ${colors.TEXT_PRIMARY};
${props =>
props.isDisabled &&
css`
pointer-events: none;
color: ${colors.TEXT_SECONDARY};
background: ${colors.GRAY_LIGHT};
`}
${props =>
props.isWhite &&
css`
background: ${colors.WHITE};
color: ${colors.TEXT_SECONDARY};
border: 1px solid ${colors.DIVIDER};
&:focus {
border-color: ${colors.INPUT_FOCUSED_BORDER};
}
&:hover {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
&:active {
color: ${colors.TEXT_PRIMARY};
background: ${colors.DIVIDER};
}
`}
${props =>
props.isTransparent &&
css`
background: transparent;
}
`}
${props => props.isWebUsb && css`
position: relative;
padding: 12px 24px 12px 40px;
background: transparent;
color: ${colors.GREEN_PRIMARY};
border: 1px solid ${colors.GREEN_PRIMARY};
transition: ${TRANSITION.HOVER};
&:before,
&:after {
content: '';
position: absolute;
background: ${colors.GREEN_PRIMARY};
top: 0;
bottom: 0;
margin: auto;
transition: ${TRANSITION.HOVER};
}
border: 0px;
color: ${colors.TEXT_SECONDARY};
&:before {
width: 12px;
height: 2px;
left: 18px;
}
&:focus {
color: ${colors.TEXT_PRIMARY};
box-shadow: none;
}
&:after {
width: 2px;
height: 12px;
left: 23px;
}
&:hover,
&:active {
color: ${colors.TEXT_PRIMARY};
background: transparent;
}
`}
&:hover {
background: ${colors.GREEN_PRIMARY};
color: ${colors.WHITE};
${props =>
props.isWebUsb &&
css`
position: relative;
padding: 12px 24px 12px 40px;
background: transparent;
color: ${colors.GREEN_PRIMARY};
border: 1px solid ${colors.GREEN_PRIMARY};
transition: ${TRANSITION.HOVER};
&:before,
&:after {
background: ${colors.WHITE};
content: '';
position: absolute;
background: ${colors.GREEN_PRIMARY};
top: 0;
bottom: 0;
margin: auto;
transition: ${TRANSITION.HOVER};
}
&:before {
width: 12px;
height: 2px;
left: 18px;
}
&:after {
width: 2px;
height: 12px;
left: 23px;
}
&:hover {
background: ${colors.GREEN_PRIMARY};
color: ${colors.WHITE};
&:before,
&:after {
background: ${colors.WHITE};
}
}
iframe {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
}
iframe {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
`}
`}
`;
const Button = ({

@ -12,7 +12,7 @@ type Props = {
onClick: (event: KeyboardEvent) => void,
isChecked: boolean,
children: React.Node,
}
};
const Wrapper = styled.div`
display: flex;
@ -26,8 +26,7 @@ const Wrapper = styled.div`
}
`;
const Tick = styled.div`
`;
const Tick = styled.div``;
const IconWrapper = styled.div`
display: flex;
@ -42,9 +41,11 @@ const IconWrapper = styled.div`
&:hover,
&:focus {
${props => !props.isChecked && css`
border: 1px solid ${colors.GREEN_PRIMARY};
`}
${props =>
!props.isChecked &&
css`
border: 1px solid ${colors.GREEN_PRIMARY};
`}
background: ${props => (props.isChecked ? colors.GREEN_PRIMARY : colors.WHITE)};
}
`;
@ -70,17 +71,9 @@ class Checkbox extends React.PureComponent<Props> {
}
render() {
const {
isChecked,
children,
onClick,
} = this.props;
const { isChecked, children, onClick } = this.props;
return (
<Wrapper
onClick={onClick}
onKeyUp={event => this.handleKeyboard(event)}
tabIndex={0}
>
<Wrapper onClick={onClick} onKeyUp={event => this.handleKeyboard(event)} tabIndex={0}>
<IconWrapper isChecked={isChecked}>
{isChecked && (
<Tick>
@ -91,8 +84,7 @@ class Checkbox extends React.PureComponent<Props> {
icon={icons.SUCCESS}
/>
</Tick>
)
}
)}
</IconWrapper>
<Label isChecked={isChecked}>{children}</Label>
</Wrapper>

@ -2,12 +2,7 @@
import React from 'react';
import styled, { css } from 'styled-components';
import PropTypes from 'prop-types';
import {
getStatusColor,
getStatusName,
getStatus,
getVersion,
} from 'utils/device';
import { getStatusColor, getStatusName, getStatus, getVersion } from 'utils/device';
import TrezorImage from 'components/images/TrezorImage';
import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
@ -27,19 +22,26 @@ const Wrapper = styled.div`
border-radius: 4px 0 0 0;
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
${props => (props.isOpen || !props.isSelected) && css`
box-shadow: none;
`}
${props =>
(props.isOpen || !props.isSelected) &&
css`
box-shadow: none;
`}
${props => props.disabled && css`
cursor: default;
`}
${props =>
props.disabled &&
css`
cursor: default;
`}
${props => props.isHoverable && !props.disabled && css`
&:hover {
background: ${colors.GRAY_LIGHT};
}
`}
${props =>
props.isHoverable &&
!props.disabled &&
css`
&:hover {
background: ${colors.GRAY_LIGHT};
}
`}
`;
const LabelWrapper = styled.div`
@ -88,7 +90,6 @@ const Dot = styled.div`
height: 10px;
`;
const DeviceHeader = ({
isOpen,
icon,
@ -120,9 +121,7 @@ const DeviceHeader = ({
<Name>{device.instanceLabel}</Name>
<Status title={getStatusName(status)}>{getStatusName(status)}</Status>
</LabelWrapper>
<IconWrapper>
{icon && !disabled && isAccessible && icon}
</IconWrapper>
<IconWrapper>{icon && !disabled && isAccessible && icon}</IconWrapper>
</Wrapper>
);
};

@ -9,7 +9,6 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import colors from 'config/colors';
import { FONT_SIZE } from 'config/variables';
import * as LogActions from 'actions/LogActions';
@ -19,7 +18,7 @@ type Props = {
opened: boolean,
isLanding: boolean,
toggle: () => any,
}
};
const Wrapper = styled.div`
width: 100%;
@ -62,17 +61,27 @@ const Footer = ({ opened, toggle, isLanding }: Props) => (
<Wrapper>
<Left>
<Copy>&copy; {getYear(new Date())}</Copy>
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink>
<StyledLink href="http://satoshilabs.com" isGreen>
SatoshiLabs
</StyledLink>
<StyledLink href="https://trezor.io/static/pdf/tos.pdf" isGreen>
<FormattedMessage {...l10nMessages.TR_TERMS} />
</StyledLink>
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink>
<StyledLink onClick={toggle} isGreen>
{opened ? 'Hide Log' : 'Show Log'}
</StyledLink>
</Left>
{!isLanding && (
<Right>
<FormattedMessage
{...l10nMessages.TR_EXCHANGE_RATES_BY}
values={{ service: <Link href="https://www.coingecko.com" isGreen>Coingecko</Link> }}
values={{
service: (
<Link href="https://www.coingecko.com" isGreen>
Coingecko
</Link>
),
}}
/>
</Right>
)}
@ -93,4 +102,7 @@ const mapDispatchToProps = dispatch => ({
toggle: bindActionCreators(LogActions.toggle, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Footer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Footer);

@ -11,26 +11,31 @@ import LanguagePicker from './index';
type StateProps = {
language: string,
}
};
type DispatchProps = {
fetchLocale: typeof WalletActions.fetchLocale,
};
type OwnProps = {
}
type OwnProps = {};
export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (
state: State
): StateProps => ({
language: state.wallet.language,
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (
dispatch: Dispatch
): DispatchProps => ({
fetchLocale: bindActionCreators(WalletActions.fetchLocale, dispatch),
});
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(LanguagePicker),
connect(
mapStateToProps,
mapDispatchToProps
)(LanguagePicker)
);

@ -80,8 +80,7 @@ const styles = {
}),
};
const buildOption = (langCode) => {
const buildOption = langCode => {
const lang = LANGUAGE.find(l => l.code === langCode);
return { value: lang.code, label: lang.name };
};
@ -99,9 +98,7 @@ const LanguagePicker = ({ language, fetchLocale }: Props) => (
isClearable={false}
onChange={option => fetchLocale(option.value)}
value={buildOption(language)}
options={
LANGUAGE.map(lang => buildOption(lang.code))
}
options={LANGUAGE.map(lang => buildOption(lang.code))}
/>
</SelectWrapper>
);

@ -53,16 +53,14 @@ const MenuToggler = styled.div`
cursor: pointer;
user-select: none;
padding: 10px 0px;
transition: all .1s ease-in;
transition: all 0.1s ease-in;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: flex;
}
`;
const TogglerText = styled.div`
`;
const TogglerText = styled.div``;
const TREZOR = styled.div``;
const T = styled.div``;
@ -131,7 +129,7 @@ const Projects = styled.div`
const A = styled.a`
color: ${colors.WHITE};
margin-left: 24px;
transition: all .1s ease-in;
transition: all 0.1s ease-in;
white-space: nowrap;
&:visited {
@ -153,33 +151,24 @@ type Props = {
sidebarEnabled?: boolean,
sidebarOpened?: ?boolean,
toggleSidebar?: toggleSidebarType,
};
const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
<Wrapper data-test="Main__page__navigation">
<LayoutWrapper>
<Left>
{ sidebarEnabled && (
{sidebarEnabled && (
<MenuToggler onClick={toggleSidebar}>
{sidebarOpened ? (
<>
<Icon
size={24}
color={colors.WHITE}
icon={icons.CLOSE}
/>
<Icon size={24} color={colors.WHITE} icon={icons.CLOSE} />
<TogglerText>
<FormattedMessage {...l10nMessages.TR_MENU_CLOSE} />
</TogglerText>
</>
) : (
<>
<Icon
color={colors.WHITE}
size={24}
icon={icons.MENU}
/>
<Icon color={colors.WHITE} size={24} icon={icons.MENU} />
<TogglerText>
<FormattedMessage {...l10nMessages.TR_MENU} />
</TogglerText>
@ -191,7 +180,15 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
<Logo>
<NavLink to="/">
<TREZOR>
<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" />
<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" />
@ -202,7 +199,15 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
</svg>
</TREZOR>
<T>
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 20 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet">
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 20 41.9"
width="100%"
height="100%"
preserveAspectRatio="xMinYMin meet"
>
<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" />
</svg>
</T>
@ -210,10 +215,18 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
</Logo>
<MenuLinks>
<Projects>
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_TREZOR} /></A>
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_WIKI} /></A>
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_BLOG} /></A>
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_SUPPORT} /></A>
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_TREZOR} />
</A>
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_WIKI} />
</A>
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_BLOG} />
</A>
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">
<FormattedMessage {...l10nMessages.TR_SUPPORT} />
</A>
</Projects>
<LanguagePicker />
</MenuLinks>

@ -22,8 +22,9 @@ const H2 = styled.h2`
font-size: ${FONT_SIZE.H2};
padding-bottom: 10px;
${props => props.claim
&& css`
${props =>
props.claim &&
css`
font-size: ${FONT_SIZE.HUGE};
padding-bottom: 24px;
`};
@ -41,6 +42,4 @@ const H4 = styled.h4`
padding-bottom: 10px;
`;
export {
H1, H2, H3, H4,
};
export { H1, H2, H3, H4 };

@ -17,7 +17,7 @@ type Props = {
onMouseLeave?: () => void,
onFocus?: () => void,
onClick?: () => void,
}
};
const chooseIconAnimationType = (canAnimate, isActive) => {
if (canAnimate) {
@ -49,11 +49,12 @@ const rotate180down = keyframes`
`;
const SvgWrapper = styled.svg`
animation: ${props => chooseIconAnimationType(props.canAnimate, props.isActive)} 0.2s linear 1 forwards;
animation: ${props => chooseIconAnimationType(props.canAnimate, props.isActive)} 0.2s linear 1
forwards;
:hover {
path {
fill: ${props => props.hoverColor}
fill: ${props => props.hoverColor};
}
}
`;
@ -93,12 +94,7 @@ const Icon = ({
onClick={onClick}
>
{icon.map(path => (
<Path
key={path}
isActive={isActive}
color={color}
d={path}
/>
<Path key={path} isActive={isActive} color={color} d={path} />
))}
</SvgWrapper>
);

@ -11,25 +11,33 @@ const A = styled.a`
transition: ${TRANSITION.HOVER};
font-size: ${FONT_SIZE.SMALL};
${props => props.isGreen && css`
text-decoration: underline;
text-decoration-color: ${colors.GREEN_PRIMARY};
`}
${props => props.isGray && css`
text-decoration: underline;
text-decoration-color: ${colors.TEXT_SECONDARY};
`}
${props =>
props.isGreen &&
css`
text-decoration: underline;
text-decoration-color: ${colors.GREEN_PRIMARY};
`}
${props =>
props.isGray &&
css`
text-decoration: underline;
text-decoration-color: ${colors.TEXT_SECONDARY};
`}
&,
&:visited,
&:active,
&:hover {
${props => props.isGreen && css`
color: ${colors.GREEN_PRIMARY};
`}
${props => props.isGray && css`
color: ${colors.TEXT_SECONDARY};
`}
${props =>
props.isGreen &&
css`
color: ${colors.GREEN_PRIMARY};
`}
${props =>
props.isGray &&
css`
color: ${colors.TEXT_SECONDARY};
`}
}
&:hover {
@ -38,13 +46,17 @@ const A = styled.a`
`;
const StyledNavLink = styled(NavLink)`
${props => props.isGreen && css`
color: ${colors.GREEN_PRIMARY};
`}
${props =>
props.isGreen &&
css`
color: ${colors.GREEN_PRIMARY};
`}
${props => props.isGray && css`
color: ${colors.TEXT_SECONDARY};
`}
${props =>
props.isGray &&
css`
color: ${colors.TEXT_SECONDARY};
`}
`;
class Link extends PureComponent {
@ -52,8 +64,7 @@ class Link extends PureComponent {
const shouldRenderRouterLink = this.props.to;
let LinkComponent;
if (shouldRenderRouterLink) {
LinkComponent = (
<StyledNavLink {...this.props}>{this.props.children}</StyledNavLink>);
LinkComponent = <StyledNavLink {...this.props}>{this.props.children}</StyledNavLink>;
} else {
LinkComponent = (
<A
@ -61,7 +72,8 @@ class Link extends PureComponent {
target={this.props.target || '_blank'}
rel="noreferrer noopener"
{...this.props}
>{this.props.children}
>
{this.props.children}
</A>
);
}

@ -24,17 +24,22 @@ const SvgWrapper = styled.svg`
`;
const CircleWrapper = styled.circle`
${props => props.isRoute && css`
stroke: ${props.transparentRoute ? 'transparent' : colors.GRAY_LIGHT};
`}
${props =>
props.isRoute &&
css`
stroke: ${props.transparentRoute ? 'transparent' : colors.GRAY_LIGHT};
`}
${props => props.isPath && css`
stroke-width: ${props.transparentRoute ? '2px' : '1px'};
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: ${DASH} 1.5s ease-in-out infinite, ${props.animationColor || GREEN_COLOR} 6s ease-in-out infinite;
stroke-linecap: round;
`};
${props =>
props.isPath &&
css`
stroke-width: ${props.transparentRoute ? '2px' : '1px'};
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: ${DASH} 1.5s ease-in-out infinite,
${props.animationColor || GREEN_COLOR} 6s ease-in-out infinite;
stroke-linecap: round;
`};
`;
const StyledParagraph = styled(Paragraph)`
@ -43,10 +48,18 @@ const StyledParagraph = styled(Paragraph)`
`;
const Loader = ({
className, text, isWhiteText = false, isSmallText, size = 100, animationColor, transparentRoute,
className,
text,
isWhiteText = false,
isSmallText,
size = 100,
animationColor,
transparentRoute,
}) => (
<Wrapper className={className} size={size}>
<StyledParagraph isSmallText={isSmallText} isWhiteText={isWhiteText}>{text}</StyledParagraph>
<StyledParagraph isSmallText={isSmallText} isWhiteText={isWhiteText}>
{text}
</StyledParagraph>
<SvgWrapper viewBox="25 25 50 50">
<CircleWrapper
animationColor={animationColor}

@ -10,7 +10,6 @@ import Icon from 'components/Icon';
import P from 'components/Paragraph';
import { FormattedMessage } from 'react-intl';
import * as LogActions from 'actions/LogActions';
import icons from 'config/icons';
import type { State, Dispatch } from 'flowtype';
@ -18,8 +17,8 @@ import l10nMessages from './index.messages';
type Props = {
log: $ElementType<State, 'log'>,
toggle: typeof LogActions.toggle
}
toggle: typeof LogActions.toggle,
};
const Wrapper = styled.div`
position: relative;
@ -86,5 +85,5 @@ export default connect(
}),
(dispatch: Dispatch) => ({
toggle: bindActionCreators(LogActions.toggle, dispatch),
}),
})
)(Log);

@ -5,7 +5,8 @@ import type { Messages } from 'flowtype/npm/react-intl';
const definedMessages: Messages = defineMessages({
TR_ATTENTION_COLON_THE_LOG_CONTAINS: {
id: 'TR_ATTENTION_COLON_THE_LOG_CONTAINS',
defaultMessage: 'Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.',
defaultMessage:
'Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.',
},
TR_LOG: {
id: 'TR_LOG',

@ -8,20 +8,18 @@ import colors from 'config/colors';
import { WHITE_COLOR } from 'config/animations';
import { getPrimaryColor } from 'utils/notification';
import Loader from 'components/Loader';
import {
TRANSITION, FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE,
} from 'config/variables';
import { TRANSITION, FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
type Props = {
type: string;
type: string,
icon?: {
type: Array<string>;
color: string;
size: number;
};
onClick: () => void;
isLoading?: boolean;
children: React.Node;
type: Array<string>,
color: string,
size: number,
},
onClick: () => void,
isLoading?: boolean,
children: React.Node,
};
const LoaderContent = styled.div`
@ -50,7 +48,7 @@ const Wrapper = styled.button`
border: 1px solid ${props => getPrimaryColor(props.type)};
transition: ${TRANSITION.HOVER};
@media screen and (max-width: ${SCREEN_SIZE.SM}){
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
padding: 12px 24px;
}
@ -64,14 +62,8 @@ const IconWrapper = styled.span`
margin-right: 8px;
`;
const NotificationButton = ({
type, icon, onClick, children, isLoading,
}: Props) => (
<Wrapper
icon={icon}
onClick={onClick}
type={type}
>
const NotificationButton = ({ type, icon, onClick, children, isLoading }: Props) => (
<Wrapper icon={icon} onClick={onClick} type={type}>
{isLoading && (
<LoaderContent type={type}>
<Loader transparentRoute animationColor={WHITE_COLOR} size={30} />
@ -79,11 +71,7 @@ const NotificationButton = ({
)}
{icon && (
<IconWrapper>
<Icon
icon={icon.type}
color={icon.color}
size={icon.size}
/>
<Icon icon={icon.type} color={icon.color} size={icon.size} />
</IconWrapper>
)}
{children}

@ -14,14 +14,14 @@ import NotificationButton from './components/NotificationButton';
type Props = {
type: string,
cancelable?: boolean;
title: ?React.Node;
className?: string;
message?: ?React.Node;
actions?: Array<CallbackAction>;
isActionInProgress?: boolean;
cancelable?: boolean,
title: ?React.Node,
className?: string,
message?: ?React.Node,
actions?: Array<CallbackAction>,
isActionInProgress?: boolean,
close?: typeof NotificationActions.close,
loading?: boolean
loading?: boolean,
};
const Wrapper = styled.div`
@ -98,7 +98,7 @@ const Notification = (props: Props): React$Element<string> => {
return (
<Wrapper className={props.className} type={props.type}>
<Content>
{props.loading && <Loader size={50} /> }
{props.loading && <Loader size={50} />}
<Body>
<IconWrapper>
<StyledIcon
@ -107,8 +107,8 @@ const Notification = (props: Props): React$Element<string> => {
/>
</IconWrapper>
<Texts>
<Title>{ props.title }</Title>
{ props.message ? <Message>{props.message}</Message> : '' }
<Title>{props.title}</Title>
{props.message ? <Message>{props.message}</Message> : ''}
</Texts>
</Body>
<AdditionalContent>
@ -119,8 +119,12 @@ const Notification = (props: Props): React$Element<string> => {
key={action.label}
type={props.type}
isLoading={props.isActionInProgress}
onClick={() => { close(); action.callback(); }}
>{action.label}
onClick={() => {
close();
action.callback();
}}
>
{action.label}
</NotificationButton>
))}
</ActionContent>
@ -128,11 +132,7 @@ const Notification = (props: Props): React$Element<string> => {
</AdditionalContent>
{props.cancelable && (
<CloseClick onClick={() => close()}>
<Icon
color={getPrimaryColor(props.type)}
icon={icons.CLOSE}
size={20}
/>
<Icon color={getPrimaryColor(props.type)} icon={icons.CLOSE} size={20} />
</CloseClick>
)}
</Content>

@ -11,16 +11,16 @@ const Wrapper = styled.p`
padding: 0;
margin: 0;
${props => props.isSmaller && css`
font-size: ${FONT_SIZE.SMALL};
`}
${props =>
props.isSmaller &&
css`
font-size: ${FONT_SIZE.SMALL};
`}
`;
const P = ({ children, className, isSmaller = false }) => (
<Wrapper
className={className}
isSmaller={isSmaller}
>{children}
<Wrapper className={className} isSmaller={isSmaller}>
{children}
</Wrapper>
);

@ -28,7 +28,7 @@ const styles = isSearchable => ({
}),
dropdownIndicator: (base, { isDisabled }) => ({
...base,
display: (isSearchable || isDisabled) ? 'none' : 'block',
display: isSearchable || isDisabled ? 'none' : 'block',
color: colors.TEXT_SECONDARY,
path: '',
'&:hover': {
@ -61,7 +61,6 @@ const styles = isSearchable => ({
}),
});
const propTypes = {
isAsync: PropTypes.bool,
isSearchable: PropTypes.bool,
@ -71,7 +70,4 @@ const AsyncSelect = props => <ReactAsyncSelect styles={styles(props.isSearchable
Select.propTypes = propTypes;
AsyncSelect.propTypes = propTypes;
export {
Select,
AsyncSelect,
};
export { Select, AsyncSelect };

@ -3,12 +3,7 @@ import Textarea from 'react-textarea-autosize';
import PropTypes from 'prop-types';
import styled, { css } from 'styled-components';
import colors from 'config/colors';
import {
FONT_SIZE,
FONT_WEIGHT,
LINE_HEIGHT,
FONT_FAMILY,
} from 'config/variables';
import { FONT_SIZE, FONT_WEIGHT, LINE_HEIGHT, FONT_FAMILY } from 'config/variables';
const Wrapper = styled.div`
width: 100%;
@ -34,11 +29,11 @@ const StyledTextarea = styled(Textarea)`
background: ${colors.WHITE};
font-weight: ${FONT_WEIGHT.MEDIUM};
font-size: ${FONT_SIZE.BASE};
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
/* placeholder styles do not work correctly when groupped into one block */
@ -98,14 +93,16 @@ const StyledTextarea = styled(Textarea)`
}
}
${props => props.trezorAction && css`
z-index: 10001; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
pointer-events: none;
`}
${props =>
props.trezorAction &&
css`
z-index: 10001; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
pointer-events: none;
`}
`;
const TopLabel = styled.span`
@ -119,7 +116,7 @@ const BottomText = styled.span`
color: ${props => (props.color ? props.color : colors.TEXT_SECONDARY)};
`;
const getColor = (inputState) => {
const getColor = inputState => {
let color = '';
if (inputState === 'success') {
color = colors.SUCCESS_PRIMARY;
@ -179,9 +176,7 @@ const TextArea = ({
trezorAction = null,
}) => (
<Wrapper className={className}>
{topLabel && (
<TopLabel>{topLabel}</TopLabel>
)}
{topLabel && <TopLabel>{topLabel}</TopLabel>}
<StyledTextarea
spellCheck="false"
autoCorrect="off"
@ -202,15 +197,10 @@ const TextArea = ({
onChange={onChange}
/>
<TrezorAction action={trezorAction}>
<ArrowUp />{trezorAction}
<ArrowUp />
{trezorAction}
</TrezorAction>
{bottomText && (
<BottomText
color={getColor(state)}
>
{bottomText}
</BottomText>
)}
{bottomText && <BottomText color={getColor(state)}>{bottomText}</BottomText>}
</Wrapper>
);

@ -45,10 +45,11 @@ const Tooltip = ({
<Content maxWidth={maxWidth}>{content}</Content>
{readMoreLink && (
<Link href={readMoreLink}>
<ReadMore><FormattedMessage {...l10nCommonMessages.TR_LEARN_MORE} /></ReadMore>
<ReadMore>
<FormattedMessage {...l10nCommonMessages.TR_LEARN_MORE} />
</ReadMore>
</Link>
)
}
)}
</ContentWrapper>
)}
>
@ -60,15 +61,9 @@ const Tooltip = ({
Tooltip.propTypes = {
className: PropTypes.string,
placement: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]),
children: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
maxWidth: PropTypes.number,
content: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]),
content: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
readMoreLink: PropTypes.string,
enterDelayMs: PropTypes.number,
};

@ -2,7 +2,6 @@
import { defineMessages } from 'react-intl';
import type { Messages } from 'flowtype/npm/react-intl';
const definedMessages: Messages = defineMessages({
});
const definedMessages: Messages = defineMessages({});
export default definedMessages;

@ -65,30 +65,55 @@ const Fee = styled.div`
border: 1px;
`;
const TransactionItem = ({
tx,
network,
}: Props) => {
const TransactionItem = ({ tx, network }: Props) => {
const url = `${network.explorer.tx}${tx.hash}`;
const date = typeof tx.timestamp === 'string' ? tx.timestamp : undefined; // TODO: format date
const addresses = (tx.type === 'send' ? tx.outputs : tx.inputs).reduce((arr, item) => arr.concat(item.addresses), []);
const addresses = (tx.type === 'send' ? tx.outputs : tx.inputs).reduce(
(arr, item) => arr.concat(item.addresses),
[]
);
const operation = tx.type === 'send' ? '-' : '+';
const amount = tx.tokens ? tx.tokens.map(t => (<Amount key={t.value}>{operation}{t.value} {t.shortcut}</Amount>)) : <Amount>{operation}{tx.total} {network.symbol}</Amount>;
const amount = tx.tokens ? (
tx.tokens.map(t => (
<Amount key={t.value}>
{operation}
{t.value} {t.shortcut}
</Amount>
))
) : (
<Amount>
{operation}
{tx.total} {network.symbol}
</Amount>
);
const fee = tx.tokens && tx.type === 'send' ? `${tx.fee} ${network.symbol}` : undefined;
return (
<Wrapper>
{ date && (<Date href={url} isGray>{ date }</Date>)}
{date && (
<Date href={url} isGray>
{date}
</Date>
)}
<Addresses>
{ addresses.map(addr => (<Address key={addr}>{addr}</Address>)) }
{ !tx.blockHeight && (
<Date href={url} isGray>Transaction hash: {tx.hash}</Date>
{addresses.map(addr => (
<Address key={addr}>{addr}</Address>
))}
{!tx.blockHeight && (
<Date href={url} isGray>
Transaction hash: {tx.hash}
</Date>
)}
</Addresses>
<Value className={tx.type}>
{amount}
{ fee && (<Fee>{operation}{fee}</Fee>) }
{fee && (
<Fee>
{operation}
{fee}
</Fee>
)}
</Value>
</Wrapper>
);

@ -42,11 +42,7 @@ class CoinLogo extends PureComponent {
return logo;
}
return (
<Wrapper className={className}>
{logo}
</Wrapper>
);
return <Wrapper className={className}>{logo}</Wrapper>;
}
}

@ -13,12 +13,12 @@ type Props = {
color?: string,
hoverColor?: string,
onClick?: any,
}
};
const SvgWrapper = styled.svg`
:hover {
path {
fill: ${props => props.hoverColor}
fill: ${props => props.hoverColor};
}
}
`;
@ -54,11 +54,7 @@ const DeviceIcon = ({
viewBox="0 0 1024 1024"
onClick={onClick}
>
<Path
key={majorVersion}
color={color}
d={getDeviceIcon(majorVersion)}
/>
<Path key={majorVersion} color={color} d={getDeviceIcon(majorVersion)} />
</SvgWrapper>
);
};

@ -5,8 +5,8 @@ import styled from 'styled-components';
import PropTypes from 'prop-types';
type Props = {
model: string;
}
model: string,
};
const Wrapper = styled.div``;

@ -7,7 +7,7 @@ import ICONS from 'config/icons';
const SvgWrapper = styled.svg`
:hover {
path {
fill: ${props => props.hoverColor}
fill: ${props => props.hoverColor};
}
}
`;

@ -4,13 +4,7 @@ import styled, { css } from 'styled-components';
import colors from 'config/colors';
import ICONS from 'config/icons';
import Icon from 'components/Icon';
import {
FONT_SIZE,
FONT_FAMILY,
FONT_WEIGHT,
LINE_HEIGHT,
TRANSITION,
} from 'config/variables';
import { FONT_SIZE, FONT_FAMILY, FONT_WEIGHT, LINE_HEIGHT, TRANSITION } from 'config/variables';
const Wrapper = styled.div`
width: 100%;
@ -48,10 +42,12 @@ const StyledInput = styled.input`
border-radius: 2px;
${props => props.hasAddon && css`
border-top-right-radius: 0;
border-bottom-right-radius: 0;
`}
${props =>
props.hasAddon &&
css`
border-top-right-radius: 0;
border-bottom-right-radius: 0;
`}
border: 1px solid ${colors.DIVIDER};
border-color: ${props => props.borderColor};
@ -75,14 +71,16 @@ const StyledInput = styled.input`
color: ${colors.TEXT_SECONDARY};
}
${props => props.trezorAction && css`
z-index: 10001;
position: relative; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
`};
${props =>
props.trezorAction &&
css`
z-index: 10001;
position: relative; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
`};
`;
const StyledIcon = styled(Icon)`
@ -99,18 +97,21 @@ const BottomText = styled.span`
`;
const Overlay = styled.div`
${props => props.isPartiallyHidden && css`
bottom: 0;
border: 1px solid ${colors.DIVIDER};
border-radius: 2px;
position: absolute;
width: 100%;
height: 100%;
background-image: linear-gradient(to right,
rgba(0,0,0, 0) 0%,
rgba(249,249,249, 1) 220px
);
`}
${props =>
props.isPartiallyHidden &&
css`
bottom: 0;
border: 1px solid ${colors.DIVIDER};
border-radius: 2px;
position: absolute;
width: 100%;
height: 100%;
background-image: linear-gradient(
to right,
rgba(0, 0, 0, 0) 0%,
rgba(249, 249, 249, 1) 220px
);
`}
`;
const TrezorAction = styled.div`
@ -168,12 +169,8 @@ class Input extends PureComponent {
render() {
return (
<Wrapper
className={this.props.className}
>
{this.props.topLabel && (
<TopLabel>{this.props.topLabel}</TopLabel>
)}
<Wrapper className={this.props.className}>
{this.props.topLabel && <TopLabel>{this.props.topLabel}</TopLabel>}
<InputWrapper>
<InputIconWrapper>
{this.props.state && (
@ -208,15 +205,14 @@ class Input extends PureComponent {
data-lpignore="true"
/>
<TrezorAction action={this.props.trezorAction}>
<ArrowUp />{this.props.trezorAction}
<ArrowUp />
{this.props.trezorAction}
</TrezorAction>
</InputIconWrapper>
{this.props.sideAddons && this.props.sideAddons.map(sideAddon => sideAddon)}
</InputWrapper>
{this.props.bottomText && (
<BottomText
color={this.getColor(this.props.state)}
>
<BottomText color={this.getColor(this.props.state)}>
{this.props.bottomText}
</BottomText>
)}

@ -33,7 +33,9 @@ type DispatchProps = {
export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (
state: State
): StateProps => ({
modal: state.modal,
accounts: state.accounts,
devices: state.devices,
@ -46,12 +48,17 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
wallet: state.wallet,
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (
dispatch: Dispatch
): DispatchProps => ({
modalActions: bindActionCreators(ModalActions, dispatch),
receiveActions: bindActionCreators(ReceiveActions, dispatch),
});
// export default connect(mapStateToProps, mapDispatchToProps)(Modal);
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Modal),
connect(
mapStateToProps,
mapDispatchToProps
)(Modal)
);

@ -63,7 +63,7 @@ type Props = {
onError?: (error: any) => any,
onCancel?: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
intl: any,
}
};
type State = {
readerLoaded: boolean,
@ -83,7 +83,7 @@ class QrModal extends Component<Props, State> {
this.setState({
readerLoaded: true,
});
}
};
handleScan = (data: string) => {
if (data) {
@ -102,7 +102,7 @@ class QrModal extends Component<Props, State> {
this.handleError(error);
}
}
}
};
handleError = (err: any) => {
// log thrown error
@ -111,8 +111,12 @@ class QrModal extends Component<Props, State> {
this.props.onError(err);
}
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError'
|| err.name === 'NotReadableError' || err.name === 'TrackStartError') {
if (
err.name === 'NotAllowedError' ||
err.name === 'PermissionDeniedError' ||
err.name === 'NotReadableError' ||
err.name === 'TrackStartError'
) {
this.setState({
error: this.props.intl.formatMessage(l10nMessages.TR_CAMERA_PERMISSION_DENIED),
});
@ -125,31 +129,34 @@ class QrModal extends Component<Props, State> {
error: this.props.intl.formatMessage(l10nMessages.TR_UNKOWN_ERROR_SEE_CONSOLE),
});
}
}
};
handleCancel = () => {
if (this.props.onCancel) {
this.props.onCancel();
}
}
};
render() {
return (
<Wrapper>
<CloseLink onClick={this.handleCancel}>
<Icon
size={24}
color={colors.TEXT_SECONDARY}
icon={icons.CLOSE}
/>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</CloseLink>
<Padding>
<H2><FormattedMessage {...l10nMessages.TR_SCAN_QR_CODE} /></H2>
{!this.state.readerLoaded && !this.state.error && <CameraPlaceholder><FormattedMessage {...l10nMessages.TR_WAITING_FOR_CAMERA} /></CameraPlaceholder>}
<H2>
<FormattedMessage {...l10nMessages.TR_SCAN_QR_CODE} />
</H2>
{!this.state.readerLoaded && !this.state.error && (
<CameraPlaceholder>
<FormattedMessage {...l10nMessages.TR_WAITING_FOR_CAMERA} />
</CameraPlaceholder>
)}
{this.state.error && (
<Error>
<ErrorTitle><FormattedMessage {...l10nMessages.TR_OOPS_SOMETHING_WENT_WRONG} /></ErrorTitle>
<ErrorTitle>
<FormattedMessage {...l10nMessages.TR_OOPS_SOMETHING_WENT_WRONG} />
</ErrorTitle>
<ErrorMessage>{this.state.error.toString()}</ErrorMessage>
</Error>
)}

@ -11,8 +11,8 @@ import { FormattedMessage } from 'react-intl';
import l10nMessages from './index.messages';
type Props = {
device: TrezorDevice;
}
device: TrezorDevice,
};
const Wrapper = styled.div``;
@ -35,5 +35,4 @@ ConfirmAction.propTypes = {
device: PropTypes.object.isRequired,
};
export default ConfirmAction;

@ -37,10 +37,7 @@ const Label = styled.div`
`;
const ConfirmAddress = (props: Props) => {
const {
account,
network,
} = props.selectedAccount;
const { account, network } = props.selectedAccount;
if (!account || !network) return null;
return (
@ -54,9 +51,13 @@ const ConfirmAddress = (props: Props) => {
</P>
</Header>
<Content>
<P>{ account.descriptor }</P>
<Label>{ network.symbol }
<FormattedMessage {...l10nCommonMessages.TR_ACCOUNT_HASH} values={{ number: account.index + 1 }} />
<P>{account.descriptor}</P>
<Label>
{network.symbol}
<FormattedMessage
{...l10nCommonMessages.TR_ACCOUNT_HASH}
values={{ number: account.index + 1 }}
/>
</Label>
</Content>
</Wrapper>

@ -18,9 +18,12 @@ import type { TrezorDevice } from 'flowtype';
import type { Props as BaseProps } from '../../Container';
type Props = {
onReceiveConfirmation: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onReceiveConfirmation'>;
device: ?TrezorDevice;
}
onReceiveConfirmation: $ElementType<
$ElementType<BaseProps, 'modalActions'>,
'onReceiveConfirmation'
>,
device: ?TrezorDevice,
};
const Wrapper = styled.div`
max-width: 370px;
@ -68,12 +71,19 @@ const Confirmation = (props: Props) => (
</StyledLink>
<H2>Your Trezor is not backed up</H2>
<Icon size={48} color={colors.WARNING_PRIMARY} icon={icons.WARNING} />
<StyledP isSmaller>If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events.</StyledP>
<StyledP isSmaller>
If your device is ever lost or damaged, your funds will be lost. Backup your device
first, to protect your coins against such events.
</StyledP>
<Row>
<Link href={`${getOldWalletUrl(props.device)}/?backup`} target="_self">
<BackupButton onClick={() => props.onReceiveConfirmation(false)}>Create a backup in 3 minutes</BackupButton>
<BackupButton onClick={() => props.onReceiveConfirmation(false)}>
Create a backup in 3 minutes
</BackupButton>
</Link>
<ProceedButton isWhite onClick={() => props.onReceiveConfirmation(true)}>Show address, I will take the risk</ProceedButton>
<ProceedButton isWhite onClick={() => props.onReceiveConfirmation(true)}>
Show address, I will take the risk
</ProceedButton>
</Row>
</Wrapper>
);

@ -15,11 +15,10 @@ import { FormattedMessage } from 'react-intl';
import type { TrezorDevice, State } from 'flowtype';
import l10nMessages from './index.messages';
type Props = {
device: TrezorDevice;
sendForm: $ElementType<State, 'sendFormEthereum'> | $ElementType<State, 'sendFormRipple'>;
}
device: TrezorDevice,
sendForm: $ElementType<State, 'sendFormEthereum'> | $ElementType<State, 'sendFormRipple'>,
};
const Wrapper = styled.div`
max-width: 390px;
@ -62,20 +61,22 @@ const FeeLevelName = styled(StyledP)`
`;
const ConfirmSignTx = (props: Props) => {
const {
amount,
address,
selectedFeeLevel,
} = props.sendForm;
const { amount, address, selectedFeeLevel } = props.sendForm;
const currency: string = typeof props.sendForm.currency === 'string' ? props.sendForm.currency : props.sendForm.networkSymbol;
const currency: string =
typeof props.sendForm.currency === 'string'
? props.sendForm.currency
: props.sendForm.networkSymbol;
return (
<Wrapper>
<Header>
<DeviceIcon device={props.device} size={60} color={colors.TEXT_SECONDARY} />
<H3>
<FormattedMessage {...l10nMessages.TR_CONFIRM_TRANSACTION_ON} values={{ deviceLabel: props.device.label }} />
<FormattedMessage
{...l10nMessages.TR_CONFIRM_TRANSACTION_ON}
values={{ deviceLabel: props.device.label }}
/>
</H3>
<P isSmaller>
<FormattedMessage {...l10nMessages.TR_DETAILS_ARE_SHOWN_ON} />
@ -85,16 +86,16 @@ const ConfirmSignTx = (props: Props) => {
<Label>
<FormattedMessage {...l10nMessages.TR_SEND_LABEL} />
</Label>
<StyledP>{`${amount} ${currency}` }</StyledP>
<StyledP>{`${amount} ${currency}`}</StyledP>
<Label>
<FormattedMessage {...l10nMessages.TR_TO_LABEL} />
</Label>
<Address>{ address }</Address>
<Address>{address}</Address>
<Label>
<FormattedMessage {...l10nMessages.TR_FEE_LABEL} />
</Label>
<FeeLevelName>{selectedFeeLevel.value}</FeeLevelName>
<StyledP>{ selectedFeeLevel.label }</StyledP>
<StyledP>{selectedFeeLevel.label}</StyledP>
</Content>
</Wrapper>
);

@ -14,7 +14,7 @@ const definedMessages: Messages = defineMessages({
TR_TO_LABEL: {
id: 'TR_TO_LABEL',
defaultMessage: 'To',
description: 'Label for recepeint\'s address',
description: "Label for recepeint's address",
},
TR_SEND_LABEL: {
id: 'TR_SEND_LABEL',

@ -19,12 +19,15 @@ import l10nMessages from './index.messages';
import type { Props as BaseProps } from '../../Container';
type Props = {
device: TrezorDevice;
account: $ElementType<$ElementType<BaseProps, 'selectedAccount'>, 'account'>;
showAddress: $ElementType<$ElementType<BaseProps, 'receiveActions'>, 'showAddress'>;
showUnverifiedAddress: $ElementType<$ElementType<BaseProps, 'receiveActions'>, 'showUnverifiedAddress'>;
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
}
device: TrezorDevice,
account: $ElementType<$ElementType<BaseProps, 'selectedAccount'>, 'account'>,
showAddress: $ElementType<$ElementType<BaseProps, 'receiveActions'>, 'showAddress'>,
showUnverifiedAddress: $ElementType<
$ElementType<BaseProps, 'receiveActions'>,
'showUnverifiedAddress'
>,
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
};
const StyledLink = styled(Link)`
position: absolute;
@ -35,7 +38,6 @@ const StyledLink = styled(Link)`
const Wrapper = styled.div`
max-width: 370px;
padding: 30px 0px;
`;
const Content = styled.div`
@ -57,7 +59,7 @@ const Row = styled.div`
display: flex;
flex-direction: column;
Button + Button {
button + button {
margin-top: 10px;
}
`;
@ -119,15 +121,27 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
let claim;
if (!device.connected) {
deviceStatus = <FormattedMessage {...l10nMessages.TR_DEVICE_LABEL_IS_NOT_CONNECTED} values={{ deviceLabel: device.label }} />;
deviceStatus = (
<FormattedMessage
{...l10nMessages.TR_DEVICE_LABEL_IS_NOT_CONNECTED}
values={{ deviceLabel: device.label }}
/>
);
claim = <FormattedMessage {...l10nMessages.TR_PLEASE_CONNECT_YOUR_DEVICE} />;
} else {
// corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings
const enable: boolean = !!(device.features && device.features.passphrase_protection);
deviceStatus = <FormattedMessage {...l10nMessages.TR_DEVICE_LABEL_IS_UNAVAILABLE} values={{ deviceLabel: device.label }} />;
claim = enable
? <FormattedMessage {...l10nMessages.TR_PLEASE_ENABLE_PASSPHRASE} />
: <FormattedMessage {...l10nMessages.TR_PLEASE_DISABLE_PASSPHRASE} />;
deviceStatus = (
<FormattedMessage
{...l10nMessages.TR_DEVICE_LABEL_IS_UNAVAILABLE}
values={{ deviceLabel: device.label }}
/>
);
claim = enable ? (
<FormattedMessage {...l10nMessages.TR_PLEASE_ENABLE_PASSPHRASE} />
) : (
<FormattedMessage {...l10nMessages.TR_PLEASE_DISABLE_PASSPHRASE} />
);
}
const needsBackup = device.features && device.features.needs_backup;
@ -138,7 +152,7 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
<StyledLink onClick={onCancel}>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<H2>{ deviceStatus }</H2>
<H2>{deviceStatus}</H2>
<StyledP isSmaller>
<FormattedMessage
{...l10nMessages.TR_TO_PREVENT_PHISHING_ATTACKS_COMMA}
@ -148,8 +162,12 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
</Content>
<Content>
<Row>
<Button onClick={() => (!account ? this.verifyAddress() : 'false')}><FormattedMessage {...l10nMessages.TR_TRY_AGAIN} /></Button>
<WarnButton isWhite onClick={() => this.showUnverifiedAddress()}><FormattedMessage {...l10nMessages.TR_SHOW_UNVERIFIED_ADDRESS} /></WarnButton>
<Button onClick={() => (!account ? this.verifyAddress() : 'false')}>
<FormattedMessage {...l10nMessages.TR_TRY_AGAIN} />
</Button>
<WarnButton isWhite onClick={() => this.showUnverifiedAddress()}>
<FormattedMessage {...l10nMessages.TR_SHOW_UNVERIFIED_ADDRESS} />
</WarnButton>
</Row>
</Content>
{needsBackup && <Divider />}
@ -157,7 +175,10 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
<>
<Content>
<H2>Device {device.label} is not backed up</H2>
<StyledP isSmaller>If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events.</StyledP>
<StyledP isSmaller>
If your device is ever lost or damaged, your funds will be lost.
Backup your device first, to protect your coins against such events.
</StyledP>
</Content>
<Content>
<Row>

@ -17,11 +17,13 @@ const definedMessages: Messages = defineMessages({
},
TR_PLEASE_ENABLE_PASSPHRASE: {
id: 'TR_PLEASE_ENABLE_PASSPHRASE',
defaultMessage: 'Please enable passphrase settings to continue with the verification process.',
defaultMessage:
'Please enable passphrase settings to continue with the verification process.',
},
TR_PLEASE_DISABLE_PASSPHRASE: {
id: 'TR_PLEASE_DISABLE_PASSPHRASE',
defaultMessage: 'Please disable passphrase settings to continue with the verification process.',
defaultMessage:
'Please disable passphrase settings to continue with the verification process.',
},
TR_SHOW_UNVERIFIED_ADDRESS: {
id: 'TR_SHOW_UNVERIFIED_ADDRESS',
@ -34,7 +36,8 @@ const definedMessages: Messages = defineMessages({
},
TR_TO_PREVENT_PHISHING_ATTACKS_COMMA: {
id: 'TR_TO_PREVENT_PHISHING_ATTACKS_COMMA',
defaultMessage: 'To prevent phishing attacks, you should verify the address on your Trezor first. {claim}',
defaultMessage:
'To prevent phishing attacks, you should verify the address on your Trezor first. {claim}',
},
});

@ -20,18 +20,18 @@ import type { TrezorDevice } from 'flowtype';
import type { Props as BaseProps } from '../../Container';
type Props = {
device: TrezorDevice;
devices: $ElementType<BaseProps, 'devices'>;
onDuplicateDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onDuplicateDevice'>;
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
}
device: TrezorDevice,
devices: $ElementType<BaseProps, 'devices'>,
onDuplicateDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onDuplicateDevice'>,
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
};
type State = {
defaultName: string;
instance: number;
instanceName: ?string;
isUsed: boolean;
}
defaultName: string,
instance: number,
instanceName: ?string,
isUsed: boolean,
};
const StyledLink = styled(Link)`
position: absolute;
@ -102,14 +102,14 @@ class DuplicateDevice extends PureComponent<Props, State> {
onNameChange = (value: string): void => {
let isUsed: boolean = false;
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({
instanceName: value.length > 0 ? value : null,
isUsed,
});
}
};
input: ?HTMLInputElement;
@ -123,25 +123,27 @@ class DuplicateDevice extends PureComponent<Props, State> {
keyboardHandler: (event: KeyboardEvent) => void;
submit() {
const extended: Object = { instanceName: this.state.instanceName, instance: this.state.instance };
const extended: Object = {
instanceName: this.state.instanceName,
instance: this.state.instance,
};
this.props.onDuplicateDevice({ ...this.props.device, ...extended });
}
render() {
const { device, onCancel } = this.props;
const {
defaultName,
instanceName,
isUsed,
} = this.state;
const { defaultName, instanceName, isUsed } = this.state;
return (
<Wrapper>
<StyledLink onClick={onCancel}>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<H3>Clone { device.label }?</H3>
<StyledP isSmaller>This will create new instance of device which can be used with different passphrase</StyledP>
<H3>Clone {device.label}?</H3>
<StyledP isSmaller>
This will create new instance of device which can be used with different
passphrase
</StyledP>
<Column>
<Label>Instance name</Label>
<Input
@ -151,22 +153,20 @@ class DuplicateDevice extends PureComponent<Props, State> {
autoCapitalize="off"
spellCheck="false"
placeholder={defaultName}
innerRef={(element) => { this.input = element; }}
innerRef={element => {
this.input = element;
}}
onChange={event => this.onNameChange(event.currentTarget.value)}
value={instanceName}
/>
{ isUsed && <ErrorMessage>Instance name is already in use</ErrorMessage> }
{isUsed && <ErrorMessage>Instance name is already in use</ErrorMessage>}
</Column>
<Column>
<StyledButton
disabled={isUsed}
onClick={() => this.submit()}
>Create new instance
<StyledButton disabled={isUsed} onClick={() => this.submit()}>
Create new instance
</StyledButton>
<StyledButton
isWhite
onClick={onCancel}
>Cancel
<StyledButton isWhite onClick={onCancel}>
Cancel
</StyledButton>
</Column>
</Wrapper>

@ -17,10 +17,13 @@ import l10nMessages from './index.messages';
import type { Props as BaseProps } from '../../Container';
type Props = {
device: TrezorDevice;
onForgetSingleDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onForgetSingleDevice'>;
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
}
device: TrezorDevice,
onForgetSingleDevice: $ElementType<
$ElementType<BaseProps, 'modalActions'>,
'onForgetSingleDevice'
>,
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
};
const Wrapper = styled.div`
width: 360px;
@ -35,7 +38,7 @@ const Row = styled.div`
display: flex;
flex-direction: column;
Button + Button {
button + button {
margin-top: 10px;
}
`;
@ -75,11 +78,17 @@ class ForgetDevice extends PureComponent<Props> {
/>
</H2>
<StyledP isSmaller>
<FormattedMessage {...l10nMessages.TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM} />
<FormattedMessage
{...l10nMessages.TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM}
/>
</StyledP>
<Row>
<Button onClick={() => this.forget()}><FormattedMessage {...l10nCommonMessages.TR_FORGET_DEVICE} /></Button>
<Button isWhite onClick={this.props.onCancel}><FormattedMessage {...l10nMessages.TR_DONT_FORGET} /></Button>
<Button onClick={() => this.forget()}>
<FormattedMessage {...l10nCommonMessages.TR_FORGET_DEVICE} />
</Button>
<Button isWhite onClick={this.props.onCancel}>
<FormattedMessage {...l10nMessages.TR_DONT_FORGET} />
</Button>
</Row>
</Wrapper>
);

@ -5,12 +5,13 @@ import type { Messages } from 'flowtype/npm/react-intl';
const definedMessages: Messages = defineMessages({
TR_DONT_FORGET: {
id: 'TR_DONT_FORGET',
defaultMessage: 'Don\'t forget',
defaultMessage: "Don't forget",
description: 'Button in remember/forget dialog',
},
TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM: {
id: 'TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM',
defaultMessage: '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.',
defaultMessage:
'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.',
},
});

@ -17,16 +17,16 @@ import l10nMessages from './index.messages';
import type { Props as BaseProps } from '../../Container';
type Props = {
device: TrezorDevice;
instances: ?Array<TrezorDevice>;
onRememberDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onRememberDevice'>;
onForgetDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onForgetDevice'>;
}
device: TrezorDevice,
instances: ?Array<TrezorDevice>,
onRememberDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onRememberDevice'>,
onForgetDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onForgetDevice'>,
};
type State = {
countdown: number;
ticker?: number;
}
countdown: number,
ticker?: number,
};
const ButtonContent = styled.div`
display: flex;
@ -52,7 +52,7 @@ const Column = styled.div`
display: flex;
flex-direction: column;
Button + Button {
button + button {
margin-top: 10px;
}
`;
@ -120,7 +120,7 @@ class RememberDevice extends PureComponent<Props, State> {
let { label } = device;
const deviceCount = instances ? instances.length : 0;
if (instances && instances.length > 0) {
label = instances.map(instance => (instance.instanceLabel)).join(',');
label = instances.map(instance => instance.instanceLabel).join(',');
}
return (
<Wrapper>
@ -154,10 +154,7 @@ class RememberDevice extends PureComponent<Props, State> {
/>
</ButtonContent>
</Button>
<Button
isWhite
onClick={() => onRememberDevice(device)}
>
<Button isWhite onClick={() => onRememberDevice(device)}>
<FormattedMessage {...l10nMessages.TR_REMEMBER_DEVICE} />
</Button>
</Column>

@ -5,7 +5,8 @@ import type { Messages } from 'flowtype/npm/react-intl';
const definedMessages: Messages = defineMessages({
TR_WOULD_YOU_LIKE_TREZOR_WALLET_TO: {
id: 'TR_WOULD_YOU_LIKE_TREZOR_WALLET_TO',
defaultMessage: 'Would you like Trezor Wallet to forget your {deviceCount, plural, one {device} other {devices}} or to remember {deviceCount, plural, one {it} other {them}}, so that it is still visible even while disconnected?',
defaultMessage:
'Would you like Trezor Wallet to forget your {deviceCount, plural, one {device} other {devices}} or to remember {deviceCount, plural, one {it} other {them}}, so that it is still visible even while disconnected?',
},
TR_REMEMBER_DEVICE: {
id: 'TR_REMEMBER_DEVICE',

@ -23,13 +23,15 @@ import type { Props as BaseProps } from '../../Container';
type Props = {
intl: any,
device: TrezorDevice;
onWalletTypeRequest: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onWalletTypeRequest'>;
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
}
device: TrezorDevice,
onWalletTypeRequest: $ElementType<
$ElementType<BaseProps, 'modalActions'>,
'onWalletTypeRequest'
>,
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
};
const Wrapper = styled.div`
`;
const Wrapper = styled.div``;
const Header = styled.div`
display: flex;
@ -71,10 +73,12 @@ const Content = styled.div`
justify-content: center;
align-items: center;
${props => props.isTop && css`
padding-top: 40px;
border-bottom: 1px solid ${colors.DIVIDER};
`}
${props =>
props.isTop &&
css`
padding-top: 40px;
border-bottom: 1px solid ${colors.DIVIDER};
`}
`;
class WalletType extends PureComponent<Props> {
@ -105,17 +109,13 @@ class WalletType extends PureComponent<Props> {
return (
<Wrapper>
{ device.state && (
{device.state && (
<StyledLink onClick={onCancel}>
<Icon
size={24}
color={colors.TEXT_SECONDARY}
icon={icons.CLOSE}
/>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
)}
<StyledHeading>{ device.state
? (
<StyledHeading>
{device.state ? (
<FormattedMessage
{...l10nMessages.TR_CHANGE_WALLET_TYPE_FOR}
values={{ deviceLabel: device.instanceLabel }}
@ -143,25 +143,21 @@ class WalletType extends PureComponent<Props> {
<Tooltip
maxWidth={285}
placement="top"
content={this.props.intl.formatMessage(l10nMessages.TR_PASSPHRASE_IS_OPTIONAL_FEATURE)}
content={this.props.intl.formatMessage(
l10nMessages.TR_PASSPHRASE_IS_OPTIONAL_FEATURE
)}
readMoreLink="https://wiki.trezor.io/Passphrase"
>
<StyledIcon
icon={icons.HELP}
color={colors.TEXT_SECONDARY}
size={26}
/>
<StyledIcon icon={icons.HELP} color={colors.TEXT_SECONDARY} size={26} />
</Tooltip>
<Header>
<WalletTypeIcon
type="hidden"
size={32}
color={colors.TEXT_PRIMARY}
/>
<WalletTypeIcon type="hidden" size={32} color={colors.TEXT_PRIMARY} />
<FormattedMessage {...l10nMessages.TR_HIDDEN_WALLET} />
</Header>
<P isSmaller>
<FormattedMessage {...l10nMessages.TR_ASKED_ENTER_YOUR_PASSPHRASE_TO_UNLOCK} />
<FormattedMessage
{...l10nMessages.TR_ASKED_ENTER_YOUR_PASSPHRASE_TO_UNLOCK}
/>
</P>
<StyledButton isWhite onClick={() => onWalletTypeRequest(true)}>
<FormattedMessage {...l10nCommonMessages.TR_GO_TO_HIDDEN_WALLET} />

@ -25,7 +25,8 @@ const definedMessages: Messages = defineMessages({
},
TR_PASSPHRASE_IS_OPTIONAL_FEATURE: {
id: 'TR_PASSPHRASE_IS_OPTIONAL_FEATURE',
defaultMessage: 'Passphrase is an optional feature of the Trezor device that is recommended for advanced users only. It is a word or a sentence of your choice. Its main purpose is to access a hidden wallet.',
defaultMessage:
'Passphrase is an optional feature of the Trezor device that is recommended for advanced users only. It is a word or a sentence of your choice. Its main purpose is to access a hidden wallet.',
},
TR_ASKED_ENTER_YOUR_PASSPHRASE_TO_UNLOCK: {
id: 'TR_ASKED_ENTER_YOUR_PASSPHRASE_TO_UNLOCK',

@ -20,8 +20,8 @@ import CardanoImage from './images/cardano.png';
import type { Props as BaseProps } from '../../Container';
type Props = {
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
}
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
};
const Wrapper = styled.div`
width: 100%;
@ -51,11 +51,7 @@ const Img = styled.img`
const CardanoWallet = (props: Props) => (
<Wrapper>
<StyledLink onClick={props.onCancel}>
<Icon
size={24}
color={colors.TEXT_SECONDARY}
icon={icons.CLOSE}
/>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<Img src={CardanoImage} />
<H2>

@ -18,8 +18,8 @@ import NemImage from './images/nem-download.png';
import type { Props as BaseProps } from '../../Container';
type Props = {
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
}
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
};
const Wrapper = styled.div`
width: 100%;
@ -47,11 +47,7 @@ const Img = styled.img`
const NemWallet = (props: Props) => (
<Wrapper>
<StyledLink onClick={props.onCancel}>
<Icon
size={24}
color={colors.TEXT_SECONDARY}
icon={icons.CLOSE}
/>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<H2>
<FormattedMessage {...l10nMessages.TR_NEM_WALLET} />

@ -9,7 +9,8 @@ const definedMessages: Messages = defineMessages({
},
TR_WE_HAVE_PARTNERED_UP_WITH_THE_NEM: {
id: 'TR_WE_HAVE_PARTNERED_UP_WITH_THE_NEM',
defaultMessage: 'We have partnered up with the NEM Foundation to provide you with a full-fledged NEM Wallet.',
defaultMessage:
'We have partnered up with the NEM Foundation to provide you with a full-fledged NEM Wallet.',
},
TR_MAKE_SURE_YOU_DOWNLOAD_THE_UNIVERSAL: {
id: 'TR_MAKE_SURE_YOU_DOWNLOAD_THE_UNIVERSAL',

@ -20,8 +20,8 @@ import StellarImage from './images/xlm.png';
import type { Props as BaseProps } from '../../Container';
type Props = {
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
}
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
};
const Wrapper = styled.div`
width: 100%;
@ -51,11 +51,7 @@ const Img = styled.img`
const StellarWallet = (props: Props) => (
<Wrapper>
<StyledLink onClick={props.onCancel}>
<Icon
size={24}
color={colors.TEXT_SECONDARY}
icon={icons.CLOSE}
/>
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
<Img src={StellarImage} />
<H2>

@ -65,12 +65,7 @@ const getDeviceContextModal = (props: Props) => {
switch (modal.windowType) {
case UI.REQUEST_PIN:
return (
<Pin
device={modal.device}
onPinSubmit={modalActions.onPinSubmit}
/>
);
return <Pin device={modal.device} onPinSubmit={modalActions.onPinSubmit} />;
case UI.INVALID_PIN:
return <InvalidPin device={modal.device} />;
@ -91,10 +86,13 @@ const getDeviceContextModal = (props: Props) => {
if (!props.selectedAccount.network) return null;
switch (props.selectedAccount.network.type) {
case 'ethereum':
return <ConfirmSignTx device={modal.device} sendForm={props.sendFormEthereum} />;
return (
<ConfirmSignTx device={modal.device} sendForm={props.sendFormEthereum} />
);
case 'ripple':
return <ConfirmSignTx device={modal.device} sendForm={props.sendFormRipple} />;
default: return null;
default:
return null;
}
}
@ -166,11 +164,11 @@ const getExternalContextModal = (props: Props) => {
switch (modal.windowType) {
case 'xem':
return (<Nem onCancel={modalActions.onCancel} />);
return <Nem onCancel={modalActions.onCancel} />;
case 'xlm':
return (<Stellar onCancel={modalActions.onCancel} />);
return <Stellar onCancel={modalActions.onCancel} />;
case 'ada':
return (<Cardano onCancel={modalActions.onCancel} />);
return <Cardano onCancel={modalActions.onCancel} />;
default:
return null;
}
@ -197,7 +195,12 @@ const getConfirmationModal = (props: Props) => {
switch (modal.windowType) {
case 'no-backup':
return (<ConfirmNoBackup device={wallet.selectedDevice} onReceiveConfirmation={modalActions.onReceiveConfirmation} />);
return (
<ConfirmNoBackup
device={wallet.selectedDevice}
onReceiveConfirmation={modalActions.onReceiveConfirmation}
/>
);
default:
return null;
}
@ -228,9 +231,7 @@ const Modal = (props: Props) => {
return (
<ModalContainer>
<ModalWindow>
{ component }
</ModalWindow>
<ModalWindow>{component}</ModalWindow>
</ModalContainer>
);
};

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import styled from 'styled-components';
import { FormattedMessage } from 'react-intl';
import colors from 'config/colors';
import { FONT_SIZE, TRANSITION } from 'config/variables';
@ -20,10 +19,10 @@ import l10nMessages from './index.messages';
import type { Props as BaseProps } from '../../Container';
type Props = {
device: TrezorDevice;
selectedDevice: ?TrezorDevice;
onPassphraseSubmit: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onPassphraseSubmit'>;
}
device: TrezorDevice,
selectedDevice: ?TrezorDevice,
onPassphraseSubmit: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onPassphraseSubmit'>,
};
type State = {
deviceLabel: string,
@ -165,7 +164,10 @@ class Passphrase extends PureComponent<Props, State> {
handleCheckboxClick() {
let match = false;
if (this.state.shouldShowSingleInput || this.state.passphraseInputValue === this.state.passphraseCheckInputValue) {
if (
this.state.shouldShowSingleInput ||
this.state.passphraseInputValue === this.state.passphraseCheckInputValue
) {
match = true;
} else {
match = !!this.state.isPassphraseHidden;
@ -215,8 +217,7 @@ class Passphrase extends PureComponent<Props, State> {
/>
</H2>
<P isSmaller>
<FormattedMessage {...l10nMessages.TR_NOTE_COLON_PASSPHRASE} />
{' '}
<FormattedMessage {...l10nMessages.TR_NOTE_COLON_PASSPHRASE} />{' '}
<FormattedMessage {...l10nMessages.TR_IF_YOU_FORGET_YOUR_PASSPHRASE_COMMA} />
</P>
<Row>
@ -224,7 +225,9 @@ class Passphrase extends PureComponent<Props, State> {
<FormattedMessage {...l10nMessages.TR_PASSPHRASE} />
</Label>
<Input
innerRef={(input) => { this.passphraseInput = input; }}
innerRef={input => {
this.passphraseInput = input;
}}
name="passphraseInputValue"
type={this.state.isPassphraseHidden ? 'password' : 'text'}
autocorrect="off"
@ -278,11 +281,10 @@ class Passphrase extends PureComponent<Props, State> {
{...l10nMessages.TR_CHANGED_YOUR_MIND}
values={{
TR_GO_TO_STANDARD_WALLET: (
<LinkButton
isGreen
onClick={() => this.submitPassphrase(true)}
>
<FormattedMessage {...l10nCommonMessages.TR_GO_TO_STANDARD_WALLET} />
<LinkButton isGreen onClick={() => this.submitPassphrase(true)}>
<FormattedMessage
{...l10nCommonMessages.TR_GO_TO_STANDARD_WALLET}
/>
</LinkButton>
),
}}

@ -13,7 +13,8 @@ const definedMessages: Messages = defineMessages({
},
TR_IF_YOU_FORGET_YOUR_PASSPHRASE_COMMA: {
id: 'TR_IF_YOU_FORGET_YOUR_PASSPHRASE_COMMA',
defaultMessage: 'If you forget your passphrase, your wallet is lost for good. There is no way to recover your funds.',
defaultMessage:
'If you forget your passphrase, your wallet is lost for good. There is no way to recover your funds.',
},
TR_CONFIRM_PASSPHRASE: {
id: 'TR_CONFIRM_PASSPHRASE',

@ -13,8 +13,8 @@ import P from 'components/Paragraph';
import type { TrezorDevice } from 'flowtype';
type Props = {
device: TrezorDevice;
}
device: TrezorDevice,
};
const Wrapper = styled.div`
max-width: 360px;
@ -27,8 +27,10 @@ const PassphraseType = (props: Props) => (
<Wrapper>
<Header>
<DeviceIcon device={props.device} size={60} color={colors.TEXT_SECONDARY} />
<H3>Complete the action on { props.device.label } device</H3>
<P isSmaller>If you enter a wrong passphrase, you will not unlock the desired hidden wallet.</P>
<H3>Complete the action on {props.device.label} device</H3>
<P isSmaller>
If you enter a wrong passphrase, you will not unlock the desired hidden wallet.
</P>
</Header>
</Wrapper>
);

@ -12,8 +12,8 @@ import type { TrezorDevice } from 'flowtype';
import l10nMessages from './index.messages';
type Props = {
device: TrezorDevice;
}
device: TrezorDevice,
};
const Wrapper = styled.div`
padding: 30px 48px;

@ -7,8 +7,8 @@ import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
type Props = {
onClick: () => void;
children: React.Node;
onClick: () => void,
children: React.Node,
};
const Wrapper = styled.button`
@ -46,11 +46,7 @@ const Wrapper = styled.button`
}
`;
const PinButton = ({ children, onClick }: Props) => (
<Wrapper onClick={onClick}>
{ children }
</Wrapper>
);
const PinButton = ({ children, onClick }: Props) => <Wrapper onClick={onClick}>{children}</Wrapper>;
PinButton.propTypes = {
children: PropTypes.string.isRequired,

@ -9,8 +9,8 @@ import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import icons from 'config/icons';
type Props = {
value: string;
onDeleteClick: () => void;
value: string,
onDeleteClick: () => void,
};
const Wrapper = styled.div`
@ -37,13 +37,7 @@ const StyledIcon = styled(Icon)`
const Input = ({ value, onDeleteClick }: Props) => (
<Wrapper>
<StyledInput
disabled
type="password"
maxLength="9"
autoComplete="off"
value={value}
/>
<StyledInput disabled type="password" maxLength="9" autoComplete="off" value={value} />
<StyledIcon onClick={onDeleteClick} color={colors.TEXT_PRIMARY} icon={icons.BACK} />
</Wrapper>
);

@ -19,13 +19,13 @@ import PinInput from './components/Input';
import type { Props as BaseProps } from '../../Container';
type Props = {
device: TrezorDevice;
onPinSubmit: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onPinSubmit'>;
}
device: TrezorDevice,
onPinSubmit: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onPinSubmit'>,
};
type State = {
pin: string;
}
pin: string,
};
const Wrapper = styled.div`
padding: 30px 48px;
@ -78,13 +78,13 @@ class Pin extends PureComponent<Props, State> {
pin,
});
}
}
};
onPinBackspace = (): void => {
this.setState(previousState => ({
pin: previousState.pin.substring(0, previousState.pin.length - 1),
}));
}
};
keyboardHandler(event: KeyboardEvent): void {
const { onPinSubmit } = this.props;
@ -138,7 +138,8 @@ class Pin extends PureComponent<Props, State> {
case 105:
this.onPinAdd(9);
break;
default: break;
default:
break;
}
}
@ -155,24 +156,44 @@ class Pin extends PureComponent<Props, State> {
values={{ deviceLabel: device.label }}
/>
</H2>
<P isSmaller><FormattedMessage {...l10nMessages.TR_THE_PIN_LAYOUT_IS_DISPLAYED_ON} /></P>
<P isSmaller>
<FormattedMessage {...l10nMessages.TR_THE_PIN_LAYOUT_IS_DISPLAYED_ON} />
</P>
<InputRow>
<PinInput value={pin} onDeleteClick={() => this.onPinBackspace()} />
</InputRow>
<PinRow>
<PinButton type="button" data-value="7" onClick={() => this.onPinAdd(7)}>&#8226; </PinButton>
<PinButton type="button" data-value="8" onClick={() => this.onPinAdd(8)}>&#8226;</PinButton>
<PinButton type="button" data-value="9" onClick={() => this.onPinAdd(9)}>&#8226;</PinButton>
<PinButton type="button" data-value="7" onClick={() => this.onPinAdd(7)}>
&#8226;{' '}
</PinButton>
<PinButton type="button" data-value="8" onClick={() => this.onPinAdd(8)}>
&#8226;
</PinButton>
<PinButton type="button" data-value="9" onClick={() => this.onPinAdd(9)}>
&#8226;
</PinButton>
</PinRow>
<PinRow>
<PinButton type="button" data-value="4" onClick={() => this.onPinAdd(4)}>&#8226; </PinButton>
<PinButton type="button" data-value="5" onClick={() => this.onPinAdd(5)}>&#8226;</PinButton>
<PinButton type="button" data-value="6" onClick={() => this.onPinAdd(6)}>&#8226;</PinButton>
<PinButton type="button" data-value="4" onClick={() => this.onPinAdd(4)}>
&#8226;{' '}
</PinButton>
<PinButton type="button" data-value="5" onClick={() => this.onPinAdd(5)}>
&#8226;
</PinButton>
<PinButton type="button" data-value="6" onClick={() => this.onPinAdd(6)}>
&#8226;
</PinButton>
</PinRow>
<PinRow>
<PinButton type="button" data-value="1" onClick={() => this.onPinAdd(1)}>&#8226; </PinButton>
<PinButton type="button" data-value="2" onClick={() => this.onPinAdd(2)}>&#8226;</PinButton>
<PinButton type="button" data-value="3" onClick={() => this.onPinAdd(3)}>&#8226;</PinButton>
<PinButton type="button" data-value="1" onClick={() => this.onPinAdd(1)}>
&#8226;{' '}
</PinButton>
<PinButton type="button" data-value="2" onClick={() => this.onPinAdd(2)}>
&#8226;
</PinButton>
<PinButton type="button" data-value="3" onClick={() => this.onPinAdd(3)}>
&#8226;
</PinButton>
</PinRow>
<Footer>
<Button type="button" onClick={() => onPinSubmit(pin)}>

@ -6,7 +6,8 @@ import type { Props } from '../../index';
export default (props: Props) => {
const { selectedDevice } = props.wallet;
const needsBackup = selectedDevice && selectedDevice.features && selectedDevice.features.needs_backup;
const needsBackup =
selectedDevice && selectedDevice.features && selectedDevice.features.needs_backup;
if (!needsBackup) return null;
return (
<Notification
@ -14,12 +15,12 @@ export default (props: Props) => {
type="warning"
title="Your Trezor is not backed up!"
message="If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events."
actions={
[{
actions={[
{
label: 'Create a backup',
callback: props.routerActions.gotoBackup,
}]
}
},
]}
/>
);
};

@ -13,13 +13,15 @@ export default (props: Props) => {
key="update-bridge"
type="warning"
title={props.intl.formatMessage(l10nMessages.TR_NEW_TREZOR_BRIDGE_IS_AVAILABLE)}
message={props.intl.formatMessage(l10nCommonMessages.TR_UPGRADE_FOR_THE_NEWEST_FEATURES_DOT)}
actions={
[{
message={props.intl.formatMessage(
l10nCommonMessages.TR_UPGRADE_FOR_THE_NEWEST_FEATURES_DOT
)}
actions={[
{
label: props.intl.formatMessage(l10nCommonMessages.TR_SHOW_DETAILS),
callback: props.routerActions.gotoBridgeUpdate,
}]
}
},
]}
/>
);
}

@ -8,20 +8,23 @@ import l10nMessages from './index.messages';
export default (props: Props) => {
const { selectedDevice } = props.wallet;
const outdated = selectedDevice && selectedDevice.features && selectedDevice.firmware === 'outdated';
const outdated =
selectedDevice && selectedDevice.features && selectedDevice.firmware === 'outdated';
if (!outdated) return null;
return (
<Notification
key="update-firmware"
type="warning"
title={props.intl.formatMessage(l10nMessages.TR_NEW_TREZOR_FIRMWARE_IS_AVAILABLE_DOT)}
message={props.intl.formatMessage(l10nCommonMessages.TR_UPGRADE_FOR_THE_NEWEST_FEATURES_DOT)}
actions={
[{
message={props.intl.formatMessage(
l10nCommonMessages.TR_UPGRADE_FOR_THE_NEWEST_FEATURES_DOT
)}
actions={[
{
label: props.intl.formatMessage(l10nCommonMessages.TR_SHOW_DETAILS),
callback: props.routerActions.gotoFirmwareUpdate,
}]
}
},
]}
/>
);
};

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

Loading…
Cancel
Save