mirror of
https://github.com/trezor/trezor-wallet
synced 2025-02-05 04:41:25 +00:00
Merge branch 'master' into fix/disable-verify-btn
This commit is contained in:
commit
2991deccf7
13
.eslintrc
13
.eslintrc
@ -1,7 +1,12 @@
|
||||
{
|
||||
"extends": [
|
||||
"eslint-config-airbnb",
|
||||
"prettier",
|
||||
"prettier/babel",
|
||||
"plugin:flowtype/recommended",
|
||||
"prettier/flowtype",
|
||||
"prettier/react",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest/recommended"
|
||||
],
|
||||
"globals": {
|
||||
@ -24,11 +29,7 @@
|
||||
"react/forbid-prop-types": 0,
|
||||
"react/destructuring-assignment": 0,
|
||||
"react/jsx-one-expression-per-line": 0,
|
||||
"react/jsx-indent": [2, 4],
|
||||
"react/jsx-indent-props": [2, 4],
|
||||
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"]}],
|
||||
"indent": [2, 4, { "SwitchCase": 1 }],
|
||||
"no-confusing-arrow": [2,{ "allowParens": true }],
|
||||
"no-console": 0,
|
||||
"no-alert": 0,
|
||||
"no-prototype-builtins": 0,
|
||||
@ -37,10 +38,12 @@
|
||||
"eol-last": 0,
|
||||
"spaced-comment": 0,
|
||||
"no-unused-expressions": 0,
|
||||
"chai-friendly/no-unused-expressions": 2
|
||||
"chai-friendly/no-unused-expressions": 2,
|
||||
"prettier/prettier": "error"
|
||||
},
|
||||
"plugins": [
|
||||
"import",
|
||||
"prettier",
|
||||
"react",
|
||||
"jest",
|
||||
"flowtype",
|
||||
|
11
.prettierrc
Normal file
11
.prettierrc
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"semi": true,
|
||||
"useTabs": false,
|
||||
"jsxBracketSameLine": false
|
||||
}
|
@ -21,6 +21,9 @@
|
||||
"lint": "run-s lint:*",
|
||||
"lint:js": "npx eslint ./src ./webpack",
|
||||
"lint:css": "npx stylelint './src/**/*.js'",
|
||||
"lint:prettier": "npx pretty-quick",
|
||||
"lint-fix": "npx eslint ./src ./webpack --fix",
|
||||
"prettier:check": "npx eslint --print-config ./src | eslint-config-prettier-check",
|
||||
"test": "run-s test:*",
|
||||
"test:unit": "npx jest",
|
||||
"test-unit:watch": "npx jest -o --watch",
|
||||
@ -108,6 +111,7 @@
|
||||
"cypress-image-snapshot": "^3.0.0",
|
||||
"eslint": "^5.13.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-config-prettier": "^4.0.0",
|
||||
"eslint-import-resolver-babel-module": "^5.0.1",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"eslint-plugin-chai-friendly": "^0.4.1",
|
||||
@ -116,10 +120,15 @@
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-jest": "^22.2.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||
"eslint-plugin-prettier": "^3.0.1",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"file-loader": "3.0.1",
|
||||
"flow-bin": "0.75.0",
|
||||
"jest": "^24.1.0",
|
||||
"prettier": "^1.16.4",
|
||||
"prettier-eslint": "^8.8.2",
|
||||
"pretty-quick": "^1.10.0",
|
||||
"prettylint": "^1.0.0",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"stylelint-config-styled-components": "^0.1.1",
|
||||
|
@ -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 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 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 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 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,
|
||||
@ -194,4 +221,4 @@ export default {
|
||||
gotoExternalWallet,
|
||||
openQrModal,
|
||||
onQrScan,
|
||||
};
|
||||
};
|
||||
|
@ -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';
|
||||
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 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;
|
||||
}
|
||||
|
||||
@ -123,4 +133,4 @@ export default {
|
||||
dispose,
|
||||
showAddress,
|
||||
showUnverifiedAddress,
|
||||
};
|
||||
};
|
||||
|
@ -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,
|
||||
@ -162,4 +168,4 @@ export default {
|
||||
clearSign,
|
||||
clearVerify,
|
||||
inputChange,
|
||||
};
|
||||
};
|
||||
|
@ -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,
|
||||
@ -93,4 +109,4 @@ export const add = (token: NetworkToken, account: Account): AsyncAction => async
|
||||
export const remove = (token: Token): Action => ({
|
||||
type: TOKEN.REMOVE,
|
||||
token,
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
||||
@ -142,4 +166,4 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
@ -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 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// get first url
|
||||
const url = networkData.web3[urlIndex];
|
||||
if (!url) {
|
||||
reject(new Error('Web3 backend is not responding'));
|
||||
return;
|
||||
}
|
||||
// get first url
|
||||
const url = networkData.web3[urlIndex];
|
||||
if (!url) {
|
||||
reject(new Error('Web3 backend is not responding'));
|
||||
return;
|
||||
}
|
||||
|
||||
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
|
||||
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
|
||||
|
||||
const onConnect = async () => {
|
||||
const latestBlock = await web3.eth.getBlockNumber();
|
||||
const gasPrice = await web3.eth.getGasPrice();
|
||||
const onConnect = async () => {
|
||||
const latestBlock = await web3.eth.getBlockNumber();
|
||||
const gasPrice = await web3.eth.getGasPrice();
|
||||
|
||||
const newInstance = {
|
||||
network,
|
||||
web3,
|
||||
chainId: networkData.chainId,
|
||||
erc20: new web3.eth.Contract(ERC20Abi),
|
||||
latestBlock,
|
||||
gasPrice,
|
||||
const newInstance = {
|
||||
network,
|
||||
web3,
|
||||
chainId: networkData.chainId,
|
||||
erc20: new web3.eth.Contract(ERC20Abi),
|
||||
latestBlock,
|
||||
gasPrice,
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: WEB3.CREATE,
|
||||
instance: newInstance,
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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);
|
||||
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,11 +21,12 @@ 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';
|
||||
|
||||
export const REQUEST_WALLET_TYPE: 'connect__request_wallet_type' = 'connect__request_wallet_type';
|
||||
export const RECEIVE_WALLET_TYPE: 'connect__receive_wallet_type' = 'connect__receive_wallet_type';
|
||||
export const UPDATE_WALLET_TYPE: 'connect__update_wallet_type' = 'connect__update_wallet_type';
|
||||
export const UPDATE_WALLET_TYPE: 'connect__update_wallet_type' = 'connect__update_wallet_type';
|
||||
|
@ -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';
|
||||
|
@ -2,4 +2,4 @@
|
||||
|
||||
export const START_SUBSCRIBE: 'blockchain__start_subscribe' = 'blockchain__start_subscribe';
|
||||
export const READY: 'blockchain__ready' = 'blockchain__ready';
|
||||
export const UPDATE_FEE: 'blockchain__update_fee' = 'blockchain__update_fee';
|
||||
export const UPDATE_FEE: 'blockchain__update_fee' = 'blockchain__update_fee';
|
||||
|
@ -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 FROM_STORAGE: 'discovery__from_storage' = 'discovery__from_storage';
|
||||
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';
|
||||
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';
|
||||
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';
|
||||
export const REMOVE: 'account__remove' = 'account__remove';
|
||||
|
@ -1,8 +1,7 @@
|
||||
/* @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';
|
||||
export const TX_REJECTED: 'pending__tx_rejected' = 'pending__tx_rejected';
|
||||
export const TX_TOKEN_ERROR: 'pending__tx_token_error' = 'pending__tx_token_error';
|
||||
export const TX_TOKEN_ERROR: 'pending__tx_token_error' = 'pending__tx_token_error';
|
||||
|
@ -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';
|
||||
|
@ -7,4 +7,4 @@ export const TX_SENDING: 'send__tx_sending' = 'send__tx_sending';
|
||||
export const TX_COMPLETE: 'send__tx_complete' = 'send__tx_complete';
|
||||
export const TX_ERROR: 'send__tx_error' = 'send__tx_error';
|
||||
export const TOGGLE_ADVANCED: 'send__toggle_advanced' = 'send__toggle_advanced';
|
||||
export const CLEAR: 'send__clear' = 'send__clear';
|
||||
export const CLEAR: 'send__clear' = 'send__clear';
|
||||
|
@ -4,4 +4,4 @@ export const INPUT_CHANGE: 'sign__verify__input__change' = 'sign__verify__input_
|
||||
export const TOUCH: 'sign__verify__input__touch' = 'sign__verify__input__touch';
|
||||
export const CLEAR_SIGN: 'sign__verify__sign__clear' = 'sign__verify__sign__clear';
|
||||
export const CLEAR_VERIFY: 'sign__verify__verify__clear' = 'sign__verify__verify__clear';
|
||||
export const ERROR: 'sign__verify__error' = 'sign__verify__error';
|
||||
export const ERROR: 'sign__verify__error' = 'sign__verify__error';
|
||||
|
@ -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,7 +1,6 @@
|
||||
/* @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';
|
||||
export const FROM_STORAGE: 'token__from_storage' = 'token__from_storage';
|
||||
export const FROM_STORAGE: 'token__from_storage' = 'token__from_storage';
|
||||
|
@ -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';
|
||||
export const SET_LANGUAGE: 'wallet__set_language' = 'wallet__set_language';
|
||||
|
@ -1,10 +1,9 @@
|
||||
/* @flow */
|
||||
|
||||
|
||||
export const START: 'web3__start' = 'web3__start';
|
||||
export const STOP: 'web3__stop' = 'web3__stop';
|
||||
export const CREATE: 'web3__create' = 'web3__create';
|
||||
export const READY: 'web3__ready' = 'web3__ready';
|
||||
export const BLOCK_UPDATED: 'web3__block_updated' = 'web3__block_updated';
|
||||
export const GAS_PRICE_UPDATED: 'web3__gas_price_updated' = 'web3__gas_price_updated';
|
||||
export const DISCONNECT: 'web3__disconnect' = 'web3__disconnect';
|
||||
export const DISCONNECT: 'web3__disconnect' = 'web3__disconnect';
|
||||
|
@ -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';
|
||||
@ -95,4 +99,4 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover
|
||||
networkType: 'ethereum',
|
||||
nonce: account.nonce,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -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 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 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: [],
|
||||
},
|
||||
@ -649,4 +767,4 @@ export default {
|
||||
onDataChange,
|
||||
onSend,
|
||||
onClear,
|
||||
};
|
||||
};
|
||||
|
@ -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 [
|
||||
{
|
||||
@ -416,4 +478,4 @@ export const getSelectedFeeLevel = (feeLevels: Array<FeeLevel>, selected: FeeLev
|
||||
selectedFeeLevel = feeLevels.find(f => f.value === 'Normal');
|
||||
}
|
||||
return selectedFeeLevel || selected;
|
||||
};
|
||||
};
|
||||
|
@ -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');
|
||||
@ -78,4 +77,4 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover
|
||||
sequence: account.sequence,
|
||||
reserve: toDecimalAmount(account.reserve, network.decimals),
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
@ -401,4 +441,4 @@ export default {
|
||||
onDestinationTagChange,
|
||||
onSend,
|
||||
onClear,
|
||||
};
|
||||
};
|
||||
|
@ -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.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};
|
||||
${props =>
|
||||
props.isWhite &&
|
||||
css`
|
||||
background: ${colors.WHITE};
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
border: 1px solid ${colors.DIVIDER};
|
||||
|
||||
&:focus {
|
||||
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
||||
}
|
||||
&:focus {
|
||||
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
background: ${colors.DIVIDER};
|
||||
}
|
||||
&:hover {
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
background: ${colors.DIVIDER};
|
||||
}
|
||||
|
||||
&:active {
|
||||
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.isTransparent &&
|
||||
css`
|
||||
background: transparent;
|
||||
}
|
||||
`}
|
||||
border: 0px;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
|
||||
${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};
|
||||
&:focus {
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: ${colors.GREEN_PRIMARY};
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
&:hover,
|
||||
&:active {
|
||||
color: ${colors.TEXT_PRIMARY};
|
||||
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 {
|
||||
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};
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: ${colors.GREEN_PRIMARY};
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
transition: ${TRANSITION.HOVER};
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
`}
|
||||
&: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;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const Button = ({
|
||||
@ -182,4 +190,4 @@ Button.propTypes = {
|
||||
dataTest: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Button;
|
||||
export default 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>© {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);
|
||||
|
@ -14,4 +14,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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``;
|
||||
@ -75,11 +73,11 @@ const Logo = styled.div`
|
||||
${T} {
|
||||
display: none;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
${TREZOR} {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: ${colors.WHITE};
|
||||
@ -95,11 +93,11 @@ const Logo = styled.div`
|
||||
/* hides full width trezor logo, shows only trezor icon */
|
||||
${TREZOR} {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
${T} {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -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>
|
||||
@ -221,4 +234,4 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default Header;
|
||||
export default Header;
|
||||
|
@ -35,4 +35,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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>
|
||||
);
|
||||
@ -117,4 +113,4 @@ Icon.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
export default Icon;
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
@ -86,4 +98,4 @@ Link.propTypes = {
|
||||
isGray: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Link;
|
||||
export default Link;
|
||||
|
@ -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);
|
||||
})
|
||||
)(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',
|
||||
@ -14,4 +15,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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,8 +48,8 @@ const Wrapper = styled.button`
|
||||
border: 1px solid ${props => getPrimaryColor(props.type)};
|
||||
transition: ${TRANSITION.HOVER};
|
||||
|
||||
@media screen and (max-width: ${SCREEN_SIZE.SM}){
|
||||
padding: 12px 24px;
|
||||
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@ -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}
|
||||
@ -102,4 +90,4 @@ NotificationButton.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default NotificationButton;
|
||||
export default NotificationButton;
|
||||
|
@ -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>
|
||||
@ -140,4 +140,4 @@ const Notification = (props: Props): React$Element<string> => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Notification;
|
||||
export default Notification;
|
||||
|
@ -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;
|
||||
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>
|
||||
);
|
||||
@ -99,4 +124,4 @@ TransactionItem.propTypes = {
|
||||
network: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default TransactionItem;
|
||||
export default TransactionItem;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
@ -71,4 +67,4 @@ DeviceIcon.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default DeviceIcon;
|
||||
export default DeviceIcon;
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -46,4 +46,4 @@ Icon.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
export default Icon;
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -30,4 +30,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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;
|
||||
export default ConfirmAction;
|
||||
|
@ -9,4 +9,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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>
|
||||
@ -67,4 +68,4 @@ ConfirmAddress.propTypes = {
|
||||
selectedAccount: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default ConfirmAddress;
|
||||
export default ConfirmAddress;
|
||||
|
@ -13,4 +13,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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>
|
||||
);
|
||||
@ -82,4 +92,4 @@ Confirmation.propTypes = {
|
||||
onReceiveConfirmation: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Confirmation;
|
||||
export default Confirmation;
|
||||
|
@ -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>
|
||||
);
|
||||
@ -105,4 +106,4 @@ ConfirmSignTx.propTypes = {
|
||||
sendForm: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default ConfirmSignTx;
|
||||
export default ConfirmSignTx;
|
||||
|
@ -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',
|
||||
@ -28,4 +28,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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>
|
||||
@ -181,4 +202,4 @@ ConfirmUnverifiedAddress.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ConfirmUnverifiedAddress;
|
||||
export default ConfirmUnverifiedAddress;
|
||||
|
@ -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,8 +36,9 @@ 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}',
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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>
|
||||
@ -181,4 +181,4 @@ DuplicateDevice.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default DuplicateDevice;
|
||||
export default DuplicateDevice;
|
||||
|
@ -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>
|
||||
);
|
||||
@ -92,4 +101,4 @@ ForgetDevice.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ForgetDevice;
|
||||
export default ForgetDevice;
|
||||
|
@ -5,13 +5,14 @@ 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.',
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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>
|
||||
@ -173,4 +170,4 @@ RememberDevice.propTypes = {
|
||||
onForgetDevice: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default RememberDevice;
|
||||
export default RememberDevice;
|
||||
|
@ -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',
|
||||
@ -13,4 +14,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -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} />
|
||||
@ -178,4 +174,4 @@ WalletType.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(WalletType);
|
||||
export default injectIntl(WalletType);
|
||||
|
@ -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',
|
||||
@ -33,4 +34,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
@ -9,4 +9,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
12
src/components/modals/external/Cardano/index.js
vendored
12
src/components/modals/external/Cardano/index.js
vendored
@ -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>
|
||||
@ -77,4 +73,4 @@ CardanoWallet.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default CardanoWallet;
|
||||
export default CardanoWallet;
|
||||
|
@ -9,4 +9,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
12
src/components/modals/external/Nem/index.js
vendored
12
src/components/modals/external/Nem/index.js
vendored
@ -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} />
|
||||
@ -75,4 +71,4 @@ NemWallet.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default NemWallet;
|
||||
export default NemWallet;
|
||||
|
@ -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',
|
||||
@ -21,4 +22,4 @@ const definedMessages: Messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export default definedMessages;
|
||||
export default definedMessages;
|
||||
|
12
src/components/modals/external/Stellar/index.js
vendored
12
src/components/modals/external/Stellar/index.js
vendored
@ -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>
|
||||
@ -77,4 +73,4 @@ StellarWallet.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default StellarWallet;
|
||||
export default StellarWallet;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user