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": [
|
"extends": [
|
||||||
"eslint-config-airbnb",
|
"eslint-config-airbnb",
|
||||||
|
"prettier",
|
||||||
|
"prettier/babel",
|
||||||
"plugin:flowtype/recommended",
|
"plugin:flowtype/recommended",
|
||||||
|
"prettier/flowtype",
|
||||||
|
"prettier/react",
|
||||||
|
"plugin:jest/recommended",
|
||||||
"plugin:jest/recommended"
|
"plugin:jest/recommended"
|
||||||
],
|
],
|
||||||
"globals": {
|
"globals": {
|
||||||
@ -24,11 +29,7 @@
|
|||||||
"react/forbid-prop-types": 0,
|
"react/forbid-prop-types": 0,
|
||||||
"react/destructuring-assignment": 0,
|
"react/destructuring-assignment": 0,
|
||||||
"react/jsx-one-expression-per-line": 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"]}],
|
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"]}],
|
||||||
"indent": [2, 4, { "SwitchCase": 1 }],
|
|
||||||
"no-confusing-arrow": [2,{ "allowParens": true }],
|
|
||||||
"no-console": 0,
|
"no-console": 0,
|
||||||
"no-alert": 0,
|
"no-alert": 0,
|
||||||
"no-prototype-builtins": 0,
|
"no-prototype-builtins": 0,
|
||||||
@ -37,10 +38,12 @@
|
|||||||
"eol-last": 0,
|
"eol-last": 0,
|
||||||
"spaced-comment": 0,
|
"spaced-comment": 0,
|
||||||
"no-unused-expressions": 0,
|
"no-unused-expressions": 0,
|
||||||
"chai-friendly/no-unused-expressions": 2
|
"chai-friendly/no-unused-expressions": 2,
|
||||||
|
"prettier/prettier": "error"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"import",
|
"import",
|
||||||
|
"prettier",
|
||||||
"react",
|
"react",
|
||||||
"jest",
|
"jest",
|
||||||
"flowtype",
|
"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": "run-s lint:*",
|
||||||
"lint:js": "npx eslint ./src ./webpack",
|
"lint:js": "npx eslint ./src ./webpack",
|
||||||
"lint:css": "npx stylelint './src/**/*.js'",
|
"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": "run-s test:*",
|
||||||
"test:unit": "npx jest",
|
"test:unit": "npx jest",
|
||||||
"test-unit:watch": "npx jest -o --watch",
|
"test-unit:watch": "npx jest -o --watch",
|
||||||
@ -108,6 +111,7 @@
|
|||||||
"cypress-image-snapshot": "^3.0.0",
|
"cypress-image-snapshot": "^3.0.0",
|
||||||
"eslint": "^5.13.0",
|
"eslint": "^5.13.0",
|
||||||
"eslint-config-airbnb": "^17.1.0",
|
"eslint-config-airbnb": "^17.1.0",
|
||||||
|
"eslint-config-prettier": "^4.0.0",
|
||||||
"eslint-import-resolver-babel-module": "^5.0.1",
|
"eslint-import-resolver-babel-module": "^5.0.1",
|
||||||
"eslint-loader": "^2.1.2",
|
"eslint-loader": "^2.1.2",
|
||||||
"eslint-plugin-chai-friendly": "^0.4.1",
|
"eslint-plugin-chai-friendly": "^0.4.1",
|
||||||
@ -116,10 +120,15 @@
|
|||||||
"eslint-plugin-import": "^2.16.0",
|
"eslint-plugin-import": "^2.16.0",
|
||||||
"eslint-plugin-jest": "^22.2.2",
|
"eslint-plugin-jest": "^22.2.2",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||||
|
"eslint-plugin-prettier": "^3.0.1",
|
||||||
"eslint-plugin-react": "^7.12.4",
|
"eslint-plugin-react": "^7.12.4",
|
||||||
"file-loader": "3.0.1",
|
"file-loader": "3.0.1",
|
||||||
"flow-bin": "0.75.0",
|
"flow-bin": "0.75.0",
|
||||||
"jest": "^24.1.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": "^9.10.1",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.2.0",
|
||||||
"stylelint-config-styled-components": "^0.1.1",
|
"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 { Action } from 'flowtype';
|
||||||
import type { Account, State } from 'reducers/AccountsReducer';
|
import type { Account, State } from 'reducers/AccountsReducer';
|
||||||
|
|
||||||
export type AccountAction = {
|
export type AccountAction =
|
||||||
type: typeof ACCOUNT.FROM_STORAGE,
|
| {
|
||||||
payload: State
|
type: typeof ACCOUNT.FROM_STORAGE,
|
||||||
} | {
|
payload: State,
|
||||||
type: typeof ACCOUNT.CREATE | typeof ACCOUNT.UPDATE,
|
}
|
||||||
payload: Account,
|
| {
|
||||||
};
|
type: typeof ACCOUNT.CREATE | typeof ACCOUNT.UPDATE,
|
||||||
|
payload: Account,
|
||||||
|
};
|
||||||
|
|
||||||
export const update = (account: Account): Action => ({
|
export const update = (account: Account): Action => ({
|
||||||
type: ACCOUNT.UPDATE,
|
type: ACCOUNT.UPDATE,
|
||||||
|
@ -4,35 +4,35 @@ import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
|||||||
import * as EthereumBlockchainActions from 'actions/ethereum/BlockchainActions';
|
import * as EthereumBlockchainActions from 'actions/ethereum/BlockchainActions';
|
||||||
import * as RippleBlockchainActions from 'actions/ripple/BlockchainActions';
|
import * as RippleBlockchainActions from 'actions/ripple/BlockchainActions';
|
||||||
|
|
||||||
import type {
|
import type { Dispatch, GetState, PromiseAction, BlockchainFeeLevel } from 'flowtype';
|
||||||
Dispatch,
|
|
||||||
GetState,
|
|
||||||
PromiseAction,
|
|
||||||
BlockchainFeeLevel,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { BlockchainBlock, BlockchainNotification, BlockchainError } from 'trezor-connect';
|
import type { BlockchainBlock, BlockchainNotification, BlockchainError } from 'trezor-connect';
|
||||||
|
|
||||||
|
export type BlockchainAction =
|
||||||
export type BlockchainAction = {
|
| {
|
||||||
type: typeof BLOCKCHAIN.READY,
|
type: typeof BLOCKCHAIN.READY,
|
||||||
} | {
|
}
|
||||||
type: typeof BLOCKCHAIN.UPDATE_FEE,
|
| {
|
||||||
shortcut: string,
|
type: typeof BLOCKCHAIN.UPDATE_FEE,
|
||||||
feeLevels: Array<BlockchainFeeLevel>,
|
shortcut: string,
|
||||||
} | {
|
feeLevels: Array<BlockchainFeeLevel>,
|
||||||
type: typeof BLOCKCHAIN.START_SUBSCRIBE,
|
}
|
||||||
shortcut: string,
|
| {
|
||||||
}
|
type: typeof BLOCKCHAIN.START_SUBSCRIBE,
|
||||||
|
shortcut: string,
|
||||||
|
};
|
||||||
|
|
||||||
// Conditionally subscribe to blockchain backend
|
// Conditionally subscribe to blockchain backend
|
||||||
// called after TrezorConnect.init successfully emits TRANSPORT.START event
|
// called after TrezorConnect.init successfully emits TRANSPORT.START event
|
||||||
// checks if there are discovery processes loaded from LocalStorage
|
// checks if there are discovery processes loaded from LocalStorage
|
||||||
// if so starts subscription to proper networks
|
// 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) {
|
if (getState().discovery.length > 0) {
|
||||||
// get unique networks
|
// get unique networks
|
||||||
const networks: Array<string> = [];
|
const networks: Array<string> = [];
|
||||||
getState().discovery.forEach((discovery) => {
|
getState().discovery.forEach(discovery => {
|
||||||
if (networks.indexOf(discovery.network) < 0) {
|
if (networks.indexOf(discovery.network) < 0) {
|
||||||
networks.push(discovery.network);
|
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 { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === networkName);
|
const network = config.networks.find(c => c.shortcut === networkName);
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
@ -67,11 +70,14 @@ export const subscribe = (networkName: string): PromiseAction<void> => async (di
|
|||||||
case 'ripple':
|
case 'ripple':
|
||||||
await dispatch(RippleBlockchainActions.subscribe(networkName));
|
await dispatch(RippleBlockchainActions.subscribe(networkName));
|
||||||
break;
|
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();
|
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
if (getState().router.location.state.network !== shortcut) return;
|
if (getState().router.location.state.network !== shortcut) return;
|
||||||
|
|
||||||
@ -86,11 +92,14 @@ export const onBlockMined = (payload: $ElementType<BlockchainBlock, 'payload'>):
|
|||||||
case 'ripple':
|
case 'ripple':
|
||||||
await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
|
await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
|
||||||
break;
|
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 shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const { config } = getState().localStorage;
|
const { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === shortcut);
|
const network = config.networks.find(c => c.shortcut === shortcut);
|
||||||
@ -104,13 +113,16 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
|
|||||||
case 'ripple':
|
case 'ripple':
|
||||||
await dispatch(RippleBlockchainActions.onNotification(payload));
|
await dispatch(RippleBlockchainActions.onNotification(payload));
|
||||||
break;
|
break;
|
||||||
default: break;
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
||||||
// disconnect and remove Web3 websocket instance if exists
|
// 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 shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const { config } = getState().localStorage;
|
const { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === shortcut);
|
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
|
// this error is handled in BlockchainReducer
|
||||||
// await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
|
// await dispatch(RippleBlockchainActions.onBlockMined(shortcut));
|
||||||
break;
|
break;
|
||||||
default: break;
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -20,63 +20,85 @@ import * as BlockchainActions from './BlockchainActions';
|
|||||||
import * as EthereumDiscoveryActions from './ethereum/DiscoveryActions';
|
import * as EthereumDiscoveryActions from './ethereum/DiscoveryActions';
|
||||||
import * as RippleDiscoveryActions from './ripple/DiscoveryActions';
|
import * as RippleDiscoveryActions from './ripple/DiscoveryActions';
|
||||||
|
|
||||||
export type DiscoveryStartAction = EthereumDiscoveryActions.DiscoveryStartAction | RippleDiscoveryActions.DiscoveryStartAction;
|
export type DiscoveryStartAction =
|
||||||
|
| EthereumDiscoveryActions.DiscoveryStartAction
|
||||||
|
| RippleDiscoveryActions.DiscoveryStartAction;
|
||||||
|
|
||||||
export type DiscoveryWaitingAction = {
|
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,
|
device: TrezorDevice,
|
||||||
network: string,
|
network: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
export type DiscoveryCompleteAction = {
|
export type DiscoveryCompleteAction = {
|
||||||
type: typeof DISCOVERY.COMPLETE,
|
type: typeof DISCOVERY.COMPLETE,
|
||||||
device: TrezorDevice,
|
device: TrezorDevice,
|
||||||
network: string,
|
network: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
export type DiscoveryAction = {
|
export type DiscoveryAction =
|
||||||
type: typeof DISCOVERY.FROM_STORAGE,
|
| {
|
||||||
payload: State
|
type: typeof DISCOVERY.FROM_STORAGE,
|
||||||
} | {
|
payload: State,
|
||||||
type: typeof DISCOVERY.STOP,
|
}
|
||||||
device: TrezorDevice
|
| {
|
||||||
} | DiscoveryStartAction
|
type: typeof DISCOVERY.STOP,
|
||||||
| DiscoveryWaitingAction
|
device: TrezorDevice,
|
||||||
| DiscoveryCompleteAction;
|
}
|
||||||
|
| DiscoveryStartAction
|
||||||
|
| DiscoveryWaitingAction
|
||||||
|
| DiscoveryCompleteAction;
|
||||||
|
|
||||||
// There are multiple async methods during discovery process (trezor-connect and blockchain actions)
|
// 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)
|
// 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) {
|
if (!process) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { deviceState, network } = process;
|
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;
|
if (!discoveryProcess) return false;
|
||||||
return discoveryProcess.interrupted;
|
return discoveryProcess.interrupted;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Private action
|
// Private action
|
||||||
// Called from "this.begin", "this.restore", "this.addAccount"
|
// 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;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
// TODO: throw error
|
// TODO: throw error
|
||||||
console.error('Start discovery: no selected device', device);
|
console.error('Start discovery: no selected device', device);
|
||||||
return;
|
return;
|
||||||
} if (selected.path !== device.path) {
|
}
|
||||||
|
if (selected.path !== device.path) {
|
||||||
console.error('Start discovery: requested device is not selected', device, selected);
|
console.error('Start discovery: requested device is not selected', device, selected);
|
||||||
return;
|
return;
|
||||||
} if (!selected.state) {
|
}
|
||||||
|
if (!selected.state) {
|
||||||
console.warn("Start discovery: Selected device wasn't authenticated yet...");
|
console.warn("Start discovery: Selected device wasn't authenticated yet...");
|
||||||
return;
|
return;
|
||||||
} if (selected.connected && !selected.available) {
|
}
|
||||||
|
if (selected.connected && !selected.available) {
|
||||||
console.warn('Start discovery: Selected device is unavailable...');
|
console.warn('Start discovery: Selected device is unavailable...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { discovery } = getState();
|
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)) {
|
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -105,7 +127,11 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean)
|
|||||||
device,
|
device,
|
||||||
network,
|
network,
|
||||||
});
|
});
|
||||||
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) {
|
} else if (
|
||||||
|
discoveryProcess.interrupted ||
|
||||||
|
discoveryProcess.waitingForDevice ||
|
||||||
|
discoveryProcess.waitingForBlockchain
|
||||||
|
) {
|
||||||
// discovery cycle was interrupted
|
// discovery cycle was interrupted
|
||||||
// start from beginning
|
// start from beginning
|
||||||
dispatch(begin(device, network));
|
dispatch(begin(device, network));
|
||||||
@ -117,7 +143,10 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean)
|
|||||||
// first iteration
|
// first iteration
|
||||||
// generate public key for this account
|
// generate public key for this account
|
||||||
// start discovery process
|
// 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 { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === networkName);
|
const network = config.networks.find(c => c.shortcut === networkName);
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
@ -165,7 +194,9 @@ const begin = (device: TrezorDevice, networkName: string): AsyncAction => async
|
|||||||
// check for interruption
|
// check for interruption
|
||||||
// corner case: DISCOVERY.START wasn't called yet, but Discovery exists in reducer created by DISCOVERY.WAITING_FOR_DEVICE action
|
// 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
|
// 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;
|
if (dispatch(isProcessInterrupted(discoveryProcess))) return;
|
||||||
|
|
||||||
// send data to reducer
|
// send data to reducer
|
||||||
@ -175,7 +206,10 @@ const begin = (device: TrezorDevice, networkName: string): AsyncAction => async
|
|||||||
dispatch(start(device, networkName));
|
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 { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
@ -185,13 +219,19 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
|
|||||||
try {
|
try {
|
||||||
switch (network.type) {
|
switch (network.type) {
|
||||||
case 'ethereum':
|
case 'ethereum':
|
||||||
account = await dispatch(EthereumDiscoveryActions.discoverAccount(device, discoveryProcess));
|
account = await dispatch(
|
||||||
|
EthereumDiscoveryActions.discoverAccount(device, discoveryProcess)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'ripple':
|
case 'ripple':
|
||||||
account = await dispatch(RippleDiscoveryActions.discoverAccount(device, discoveryProcess));
|
account = await dispatch(
|
||||||
|
RippleDiscoveryActions.discoverAccount(device, discoveryProcess)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`);
|
throw new Error(
|
||||||
|
`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// handle not supported firmware error
|
// handle not supported firmware error
|
||||||
@ -242,7 +282,11 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
|
|||||||
if (dispatch(isProcessInterrupted(discoveryProcess))) return;
|
if (dispatch(isProcessInterrupted(discoveryProcess))) return;
|
||||||
|
|
||||||
const accountIsEmpty = account.empty;
|
const accountIsEmpty = account.empty;
|
||||||
if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && accountIndex === 0)) {
|
if (
|
||||||
|
!accountIsEmpty ||
|
||||||
|
(accountIsEmpty && completed) ||
|
||||||
|
(accountIsEmpty && accountIndex === 0)
|
||||||
|
) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACCOUNT.CREATE,
|
type: ACCOUNT.CREATE,
|
||||||
payload: account,
|
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({
|
await TrezorConnect.getFeatures({
|
||||||
device: {
|
device: {
|
||||||
path: device.path,
|
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));
|
await dispatch(BlockchainActions.subscribe(network));
|
||||||
dispatch(restore());
|
dispatch(restore());
|
||||||
};
|
};
|
||||||
@ -296,10 +344,16 @@ export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetStat
|
|||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
|
|
||||||
// find discovery process for requested network
|
// 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
|
// 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) {
|
if (shouldStart) {
|
||||||
dispatch(start(selected, urlParams.network));
|
dispatch(start(selected, urlParams.network));
|
||||||
}
|
}
|
||||||
@ -310,7 +364,9 @@ export const stop = (): ThunkAction => (dispatch: Dispatch, getState: GetState):
|
|||||||
if (!device) return;
|
if (!device) return;
|
||||||
|
|
||||||
// get all uncompleted discovery processes which assigned to selected device
|
// 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) {
|
if (discoveryProcesses.length > 0) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: DISCOVERY.STOP,
|
type: DISCOVERY.STOP,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
import * as ACCOUNT from 'actions/constants/account';
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
import * as TOKEN from 'actions/constants/token';
|
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 Erc20AbiJSON from 'public/data/ERC20Abi.json';
|
||||||
import AppConfigJSON from 'public/data/appConfig.json';
|
import AppConfigJSON from 'public/data/appConfig.json';
|
||||||
|
|
||||||
export type StorageAction = {
|
export type StorageAction =
|
||||||
type: typeof STORAGE.READY,
|
| {
|
||||||
config: Config,
|
type: typeof STORAGE.READY,
|
||||||
tokens: TokensCollection,
|
config: Config,
|
||||||
ERC20Abi: Array<TokensCollection>
|
tokens: TokensCollection,
|
||||||
} | {
|
ERC20Abi: Array<TokensCollection>,
|
||||||
type: typeof STORAGE.SAVE,
|
}
|
||||||
network: string,
|
| {
|
||||||
} | {
|
type: typeof STORAGE.SAVE,
|
||||||
type: typeof STORAGE.ERROR,
|
network: string,
|
||||||
error: string,
|
}
|
||||||
};
|
| {
|
||||||
|
type: typeof STORAGE.ERROR,
|
||||||
|
error: string,
|
||||||
|
};
|
||||||
|
|
||||||
const TYPE: 'local' = 'local';
|
const TYPE: 'local' = 'local';
|
||||||
const { STORAGE_PATH } = storageUtils;
|
const { STORAGE_PATH } = storageUtils;
|
||||||
@ -60,16 +62,39 @@ const KEY_LANGUAGE: string = `${STORAGE_PATH}language`;
|
|||||||
// or
|
// or
|
||||||
// https://www.npmjs.com/package/redux-react-session
|
// 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 => {
|
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 accounts: Array<Account> = findAccounts(devices, getState().accounts);
|
||||||
const tokens: Array<Token> = findTokens(accounts, getState().tokens);
|
const tokens: Array<Token> = findTokens(accounts, getState().tokens);
|
||||||
const pending: Array<Transaction> = findPendingTxs(accounts, getState().pending);
|
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
|
// check if device was added/ removed
|
||||||
// const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue);
|
// const newDevices: Array<TrezorDevice> = JSON.parse(event.newValue);
|
||||||
// const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features);
|
// const myDevices: Array<TrezorDevice> = getState().connect.devices.filter(d => d.features);
|
||||||
|
|
||||||
// if (newDevices.length !== myDevices.length) {
|
// if (newDevices.length !== myDevices.length) {
|
||||||
// const diff = myDevices.filter(d => newDevices.indexOf(d) < 0)
|
// const diff = myDevices.filter(d => newDevices.indexOf(d) < 0)
|
||||||
// console.warn("DEV LIST CHANGED!", newDevices.length, myDevices.length, diff)
|
// console.warn("DEV LIST CHANGED!", newDevices.length, myDevices.length, diff)
|
||||||
// // check if difference is caused by local device which is not saved
|
// // check if difference is caused by local device which is not saved
|
||||||
// // or device which was saved in other tab
|
// // or device which was saved in other tab
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// const diff = oldDevices.filter(d => newDevices.indexOf())
|
// 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');
|
const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json');
|
||||||
|
|
||||||
window.addEventListener('storage', (event) => {
|
window.addEventListener('storage', event => {
|
||||||
dispatch(update(event));
|
dispatch(update(event));
|
||||||
});
|
});
|
||||||
|
|
||||||
// load tokens
|
// load tokens
|
||||||
const tokens = await config.networks.reduce(async (promise: Promise<TokensCollection>, network: Network): Promise<TokensCollection> => {
|
const tokens = await config.networks.reduce(
|
||||||
const collection: TokensCollection = await promise;
|
async (
|
||||||
if (network.tokens) {
|
promise: Promise<TokensCollection>,
|
||||||
const json = await httpRequest(network.tokens, 'json');
|
network: Network
|
||||||
collection[network.shortcut] = json;
|
): Promise<TokensCollection> => {
|
||||||
}
|
const collection: TokensCollection = await promise;
|
||||||
return collection;
|
if (network.tokens) {
|
||||||
}, Promise.resolve({}));
|
const json = await httpRequest(network.tokens, 'json');
|
||||||
|
collection[network.shortcut] = json;
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
},
|
||||||
|
Promise.resolve({})
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: STORAGE.READY,
|
type: STORAGE.READY,
|
||||||
@ -230,7 +259,9 @@ const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (buildUtils.isDev() || buildUtils.isBeta()) {
|
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) {
|
if (!betaModal) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: WALLET.SHOW_BETA_DISCLAIMER,
|
type: WALLET.SHOW_BETA_DISCLAIMER,
|
||||||
|
@ -2,19 +2,20 @@
|
|||||||
|
|
||||||
import * as LOG from 'actions/constants/log';
|
import * as LOG from 'actions/constants/log';
|
||||||
|
|
||||||
import type {
|
import type { Action, ThunkAction, GetState, Dispatch } from 'flowtype';
|
||||||
Action, ThunkAction, GetState, Dispatch,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { LogEntry } from 'reducers/LogReducer';
|
import type { LogEntry } from 'reducers/LogReducer';
|
||||||
|
|
||||||
export type LogAction = {
|
export type LogAction =
|
||||||
type: typeof LOG.OPEN,
|
| {
|
||||||
} | {
|
type: typeof LOG.OPEN,
|
||||||
type: typeof LOG.CLOSE,
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof LOG.ADD,
|
type: typeof LOG.CLOSE,
|
||||||
payload: LogEntry
|
}
|
||||||
};
|
| {
|
||||||
|
type: typeof LOG.ADD,
|
||||||
|
payload: LogEntry,
|
||||||
|
};
|
||||||
|
|
||||||
export const toggle = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const toggle = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
if (!getState().log.opened) {
|
if (!getState().log.opened) {
|
||||||
|
@ -6,25 +6,25 @@ import type { Device } from 'trezor-connect';
|
|||||||
import * as MODAL from 'actions/constants/modal';
|
import * as MODAL from 'actions/constants/modal';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
|
|
||||||
import type {
|
import type { ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice } from 'flowtype';
|
||||||
ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { State } from 'reducers/ModalReducer';
|
import type { State } from 'reducers/ModalReducer';
|
||||||
import type { parsedURI } from 'utils/cryptoUriParser';
|
import type { parsedURI } from 'utils/cryptoUriParser';
|
||||||
|
|
||||||
import sendEthereumFormActions from './ethereum/SendFormActions';
|
import sendEthereumFormActions from './ethereum/SendFormActions';
|
||||||
import sendRippleFormActions from './ripple/SendFormActions';
|
import sendRippleFormActions from './ripple/SendFormActions';
|
||||||
|
|
||||||
export type ModalAction = {
|
export type ModalAction =
|
||||||
type: typeof MODAL.CLOSE
|
| {
|
||||||
} | {
|
type: typeof MODAL.CLOSE,
|
||||||
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
|
}
|
||||||
id: string,
|
| {
|
||||||
url: string,
|
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
|
||||||
} | {
|
id: string,
|
||||||
type: typeof MODAL.OPEN_SCAN_QR,
|
url: string,
|
||||||
};
|
}
|
||||||
|
| {
|
||||||
|
type: typeof MODAL.OPEN_SCAN_QR,
|
||||||
|
};
|
||||||
|
|
||||||
export const onPinSubmit = (value: string): Action => {
|
export const onPinSubmit = (value: string): Action => {
|
||||||
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
|
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
|
||||||
@ -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();
|
const { modal } = getState();
|
||||||
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
|
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({
|
await TrezorConnect.uiResponse({
|
||||||
type: UI.RECEIVE_CONFIRMATION,
|
type: UI.RECEIVE_CONFIRMATION,
|
||||||
payload: confirmation,
|
payload: confirmation,
|
||||||
@ -89,7 +94,9 @@ export const onCancel = (): Action => ({
|
|||||||
type: MODAL.CLOSE,
|
type: MODAL.CLOSE,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
|
export const onDuplicateDevice = (device: TrezorDevice): ThunkAction => (
|
||||||
|
dispatch: Dispatch
|
||||||
|
): void => {
|
||||||
dispatch(onCancel());
|
dispatch(onCancel());
|
||||||
|
|
||||||
dispatch({
|
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;
|
const state: State = getState().modal;
|
||||||
// handle case where forget modal is already opened
|
// handle case where forget modal is already opened
|
||||||
// TODO: 2 modals at once (two devices disconnected in the same time)
|
// 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)
|
// forget current (new)
|
||||||
if (state.context === MODAL.CONTEXT_DEVICE) {
|
if (state.context === MODAL.CONTEXT_DEVICE) {
|
||||||
dispatch({
|
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)
|
// interrupt process of remembering device (force forget)
|
||||||
// TODO: the same for disconnect more than 1 device at once
|
// TODO: the same for disconnect more than 1 device at once
|
||||||
const { modal } = getState();
|
const { modal } = getState();
|
||||||
if (modal.context === MODAL.CONTEXT_DEVICE && modal.windowType === CONNECT.REMEMBER_REQUEST) {
|
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({
|
dispatch({
|
||||||
type: MODAL.CLOSE,
|
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();
|
const { modal } = getState();
|
||||||
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
|
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
|
||||||
dispatch({
|
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({
|
dispatch({
|
||||||
type: MODAL.OPEN_EXTERNAL_WALLET,
|
type: MODAL.OPEN_EXTERNAL_WALLET,
|
||||||
id,
|
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;
|
const { address = '', amount } = parsedUri;
|
||||||
switch (networkType) {
|
switch (networkType) {
|
||||||
case 'ethereum':
|
case 'ethereum':
|
||||||
@ -180,7 +208,6 @@ export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
onPinSubmit,
|
onPinSubmit,
|
||||||
onPassphraseSubmit,
|
onPassphraseSubmit,
|
||||||
@ -194,4 +221,4 @@ export default {
|
|||||||
gotoExternalWallet,
|
gotoExternalWallet,
|
||||||
openQrModal,
|
openQrModal,
|
||||||
onQrScan,
|
onQrScan,
|
||||||
};
|
};
|
||||||
|
@ -2,41 +2,48 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
|
|
||||||
import type {
|
import type { Action, AsyncAction, GetState, Dispatch, RouterLocationState } from 'flowtype';
|
||||||
Action, AsyncAction, GetState, Dispatch, RouterLocationState,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { CallbackAction } from 'reducers/NotificationReducer';
|
import type { CallbackAction } from 'reducers/NotificationReducer';
|
||||||
|
|
||||||
export type NotificationAction = {
|
export type NotificationAction =
|
||||||
type: typeof NOTIFICATION.ADD,
|
| {
|
||||||
payload: {
|
type: typeof NOTIFICATION.ADD,
|
||||||
+type: string,
|
payload: {
|
||||||
+title: React.Node | string,
|
+type: string,
|
||||||
+message?: ?(React.Node | string),
|
+title: React.Node | string,
|
||||||
+cancelable: boolean,
|
+message?: ?(React.Node | string),
|
||||||
actions?: Array<CallbackAction>
|
+cancelable: boolean,
|
||||||
}
|
actions?: Array<CallbackAction>,
|
||||||
} | {
|
},
|
||||||
type: typeof NOTIFICATION.CLOSE,
|
}
|
||||||
payload?: {
|
| {
|
||||||
id?: string;
|
type: typeof NOTIFICATION.CLOSE,
|
||||||
devicePath?: string
|
payload?: {
|
||||||
}
|
id?: string,
|
||||||
}
|
devicePath?: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const close = (payload: any = {}): Action => ({
|
export const close = (payload: any = {}): Action => ({
|
||||||
type: NOTIFICATION.CLOSE,
|
type: NOTIFICATION.CLOSE,
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// called from RouterService
|
// 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...)
|
// if route has been changed from device view into something else (like other device, settings...)
|
||||||
// try to remove all Notifications which are linked to previous device (they are not cancelable by user)
|
// try to remove all Notifications which are linked to previous device (they are not cancelable by user)
|
||||||
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
|
if (
|
||||||
const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string');
|
currentParams.device !== requestedParams.device ||
|
||||||
entries.forEach((entry) => {
|
currentParams.deviceInstance !== requestedParams.deviceInstance
|
||||||
|
) {
|
||||||
|
const entries = getState().notifications.filter(
|
||||||
|
entry => typeof entry.devicePath === 'string'
|
||||||
|
);
|
||||||
|
entries.forEach(entry => {
|
||||||
if (typeof entry.devicePath === 'string') {
|
if (typeof entry.devicePath === 'string') {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: NOTIFICATION.CLOSE,
|
type: NOTIFICATION.CLOSE,
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
|
||||||
import type { Transaction } from 'flowtype';
|
import type { Transaction } from 'flowtype';
|
||||||
import type { State } from 'reducers/PendingTxReducer';
|
import type { State } from 'reducers/PendingTxReducer';
|
||||||
|
|
||||||
export type PendingTxAction = {
|
export type PendingTxAction =
|
||||||
type: typeof PENDING.FROM_STORAGE,
|
| {
|
||||||
payload: State
|
type: typeof PENDING.FROM_STORAGE,
|
||||||
} | {
|
payload: State,
|
||||||
type: typeof PENDING.ADD,
|
}
|
||||||
payload: Transaction
|
| {
|
||||||
} | {
|
type: typeof PENDING.ADD,
|
||||||
type: typeof PENDING.TX_RESOLVED,
|
payload: Transaction,
|
||||||
hash: string,
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof PENDING.TX_REJECTED,
|
type: typeof PENDING.TX_RESOLVED,
|
||||||
hash: string,
|
hash: string,
|
||||||
} | {
|
}
|
||||||
type: typeof PENDING.TX_TOKEN_ERROR,
|
| {
|
||||||
hash: string,
|
type: typeof PENDING.TX_REJECTED,
|
||||||
}
|
hash: string,
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: typeof PENDING.TX_TOKEN_ERROR,
|
||||||
|
hash: string,
|
||||||
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import * as RECEIVE from 'actions/constants/receive';
|
import * as RECEIVE from 'actions/constants/receive';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
@ -8,25 +7,29 @@ import * as NOTIFICATION from 'actions/constants/notification';
|
|||||||
import { initialState } from 'reducers/ReceiveReducer';
|
import { initialState } from 'reducers/ReceiveReducer';
|
||||||
import type { State } from 'reducers/ReceiveReducer';
|
import type { State } from 'reducers/ReceiveReducer';
|
||||||
|
|
||||||
import type {
|
import type { TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch } from 'flowtype';
|
||||||
TrezorDevice, ThunkAction, AsyncAction, Action, GetState, Dispatch,
|
|
||||||
} from 'flowtype';
|
|
||||||
|
|
||||||
export type ReceiveAction = {
|
export type ReceiveAction =
|
||||||
type: typeof RECEIVE.INIT,
|
| {
|
||||||
state: State
|
type: typeof RECEIVE.INIT,
|
||||||
} | {
|
state: State,
|
||||||
type: typeof RECEIVE.DISPOSE,
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof RECEIVE.REQUEST_UNVERIFIED,
|
type: typeof RECEIVE.DISPOSE,
|
||||||
device: TrezorDevice
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof RECEIVE.SHOW_ADDRESS
|
type: typeof RECEIVE.REQUEST_UNVERIFIED,
|
||||||
} | {
|
device: TrezorDevice,
|
||||||
type: typeof RECEIVE.HIDE_ADDRESS
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS
|
type: typeof RECEIVE.SHOW_ADDRESS,
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: typeof RECEIVE.HIDE_ADDRESS,
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: typeof RECEIVE.SHOW_UNVERIFIED_ADDRESS,
|
||||||
|
};
|
||||||
|
|
||||||
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
const state: State = {
|
const state: State = {
|
||||||
@ -48,7 +51,10 @@ export const showUnverifiedAddress = (): Action => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
//export const showAddress = (address_n: string): AsyncAction => {
|
//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 selected = getState().wallet.selectedDevice;
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
|
|
||||||
@ -81,7 +87,11 @@ export const showAddress = (path: Array<number>): AsyncAction => async (dispatch
|
|||||||
response = await TrezorConnect.rippleGetAddress(params);
|
response = await TrezorConnect.rippleGetAddress(params);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
response = { payload: { error: `ReceiveActions.showAddress: Unknown network type: ${network.type}` } };
|
response = {
|
||||||
|
payload: {
|
||||||
|
error: `ReceiveActions.showAddress: Unknown network type: ${network.type}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,4 +133,4 @@ export default {
|
|||||||
dispose,
|
dispose,
|
||||||
showAddress,
|
showAddress,
|
||||||
showUnverifiedAddress,
|
showUnverifiedAddress,
|
||||||
};
|
};
|
||||||
|
@ -18,9 +18,11 @@ import type {
|
|||||||
import type { RouterAction } from 'connected-react-router';
|
import type { RouterAction } from 'connected-react-router';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse url string to RouterLocationState object (key/value)
|
* Parse url string to RouterLocationState object (key/value)
|
||||||
*/
|
*/
|
||||||
export const pathToParams = (path: string): PayloadAction<RouterLocationState> => (): RouterLocationState => {
|
export const pathToParams = (
|
||||||
|
path: string
|
||||||
|
): PayloadAction<RouterLocationState> => (): RouterLocationState => {
|
||||||
// split url into parts
|
// split url into parts
|
||||||
const parts: Array<string> = path.split('/').slice(1);
|
const parts: Array<string> = path.split('/').slice(1);
|
||||||
const params: RouterLocationState = {};
|
const params: RouterLocationState = {};
|
||||||
@ -46,10 +48,13 @@ export const pathToParams = (path: string): PayloadAction<RouterLocationState> =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* RouterLocationState validation
|
* RouterLocationState validation
|
||||||
* Check if requested device or network exists in reducers
|
* Check if requested device or network exists in reducers
|
||||||
*/
|
*/
|
||||||
export const paramsValidation = (params: RouterLocationState): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
export const paramsValidation = (params: RouterLocationState): PayloadAction<boolean> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): boolean => {
|
||||||
// validate requested device
|
// validate requested device
|
||||||
|
|
||||||
if (params.hasOwnProperty('device')) {
|
if (params.hasOwnProperty('device')) {
|
||||||
@ -57,9 +62,18 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
|
|||||||
|
|
||||||
let device: ?TrezorDevice;
|
let device: ?TrezorDevice;
|
||||||
if (params.hasOwnProperty('deviceInstance')) {
|
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 {
|
} 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;
|
if (!device) return false;
|
||||||
@ -87,12 +101,16 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Composing url string from given RouterLocationState object
|
* Composing url string from given RouterLocationState object
|
||||||
* Filters unrecognized fields and sorting in correct order
|
* Filters unrecognized fields and sorting in correct order
|
||||||
*/
|
*/
|
||||||
export const paramsToPath = (params: RouterLocationState): PayloadAction<?string> => (): ?string => {
|
export const paramsToPath = (
|
||||||
|
params: RouterLocationState
|
||||||
|
): PayloadAction<?string> => (): ?string => {
|
||||||
// get patterns (fields) from routes and sort them by complexity
|
// 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
|
// find pattern
|
||||||
const keys: Array<string> = Object.keys(params);
|
const keys: Array<string> = Object.keys(params);
|
||||||
@ -111,7 +129,7 @@ export const paramsToPath = (params: RouterLocationState): PayloadAction<?string
|
|||||||
|
|
||||||
// compose url string from pattern
|
// compose url string from pattern
|
||||||
let url: string = '';
|
let url: string = '';
|
||||||
patternToUse.forEach((field) => {
|
patternToUse.forEach(field => {
|
||||||
if (field === params[field]) {
|
if (field === params[field]) {
|
||||||
// standalone (odd) fields
|
// standalone (odd) fields
|
||||||
url += `/${field}`;
|
url += `/${field}`;
|
||||||
@ -127,7 +145,10 @@ export const paramsToPath = (params: RouterLocationState): PayloadAction<?string
|
|||||||
return url;
|
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 { location } = getState().router;
|
||||||
const { firstLocationChange } = getState().wallet;
|
const { firstLocationChange } = getState().wallet;
|
||||||
// redirect to landing page (loading screen)
|
// 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
|
// example 2 (invalid blocking): url changes while passphrase modal opened because device disconnect
|
||||||
const currentParams = dispatch(pathToParams(location.pathname));
|
const currentParams = dispatch(pathToParams(location.pathname));
|
||||||
const currentParamsAreValid = dispatch(paramsValidation(currentParams));
|
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
|
// there are no connected devices or application isn't ready or initialization error occurred
|
||||||
// redirect to landing page
|
// 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));
|
const landingPageUrl = dispatch(isLandingPageUrl(requestedUrl));
|
||||||
if (shouldBeLandingPage) {
|
if (shouldBeLandingPage) {
|
||||||
const landingPageRoute = dispatch(isLandingPageUrl(requestedUrl, getState().wallet.ready));
|
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
|
* Compose url from requested device object and returns url
|
||||||
*/
|
*/
|
||||||
const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> => (dispatch: Dispatch, getState: GetState): ?string => {
|
const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): ?string => {
|
||||||
let url: ?string;
|
let url: ?string;
|
||||||
if (!device.features) {
|
if (!device.features) {
|
||||||
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
|
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`;
|
url = `/device/${device.path}/bootloader`;
|
||||||
} else if (device.mode === 'initialize') {
|
} else if (device.mode === 'initialize') {
|
||||||
url = `/device/${device.features.device_id}/initialize`;
|
url = `/device/${device.features.device_id}/initialize`;
|
||||||
@ -207,7 +237,9 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
|
|||||||
if (!device.hasOwnProperty('ts')) {
|
if (!device.hasOwnProperty('ts')) {
|
||||||
// it is device from trezor-connect triggered by DEVICE.CONNECT event
|
// it is device from trezor-connect triggered by DEVICE.CONNECT event
|
||||||
// need to lookup if there are unavailable instances
|
// 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);
|
const latest: Array<TrezorDevice> = sortDevices(available);
|
||||||
if (latest.length > 0 && latest[0].instance) {
|
if (latest.length > 0 && latest[0].instance) {
|
||||||
url += `:${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:
|
* Try to find first available device using order:
|
||||||
* 1. First unacquired
|
* 1. First unacquired
|
||||||
* 2. First connected
|
* 2. First connected
|
||||||
* 3. Saved with latest timestamp
|
* 3. Saved with latest timestamp
|
||||||
* OR redirect to landing page
|
* OR redirect to landing page
|
||||||
*/
|
*/
|
||||||
export const getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (dispatch: Dispatch, getState: GetState): ?string => {
|
export const getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): ?string => {
|
||||||
const { devices } = getState();
|
const { devices } = getState();
|
||||||
let url: ?string;
|
let url: ?string;
|
||||||
if (devices.length > 0) {
|
if (devices.length > 0) {
|
||||||
@ -241,20 +276,24 @@ export const getFirstAvailableDeviceUrl = (): PayloadAction<?string> => (dispatc
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Utility used in "getDeviceUrl" and "getFirstAvailableDeviceUrl"
|
* Utility used in "getDeviceUrl" and "getFirstAvailableDeviceUrl"
|
||||||
* sorting device array by "ts" (timestamp) field
|
* sorting device array by "ts" (timestamp) field
|
||||||
*/
|
*/
|
||||||
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => devices.sort((a, b) => {
|
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> =>
|
||||||
if (!a.ts || !b.ts) {
|
devices.sort((a, b) => {
|
||||||
return -1;
|
if (!a.ts || !b.ts) {
|
||||||
}
|
return -1;
|
||||||
return a.ts > b.ts ? -1 : 1;
|
}
|
||||||
});
|
return a.ts > b.ts ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Redirect to requested device
|
* Redirect to requested device
|
||||||
*/
|
*/
|
||||||
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
if (dispatch(setInitialUrl())) return;
|
if (dispatch(setInitialUrl())) return;
|
||||||
|
|
||||||
const url: ?string = dispatch(getDeviceUrl(device));
|
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 currentParams: RouterLocationState = getState().router.location.state;
|
||||||
const requestedParams = dispatch(pathToParams(url));
|
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));
|
dispatch(goto(url));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Redirect to first device or landing page
|
* Redirect to first device or landing page
|
||||||
*/
|
*/
|
||||||
export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const url = dispatch(getFirstAvailableDeviceUrl());
|
const url = dispatch(getFirstAvailableDeviceUrl());
|
||||||
if (url) {
|
if (url) {
|
||||||
const currentParams = getState().router.location.state;
|
const currentParams = getState().router.location.state;
|
||||||
const requestedParams = dispatch(pathToParams(url));
|
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));
|
dispatch(goto(url));
|
||||||
}
|
}
|
||||||
} else {
|
} 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 => {
|
const goto = (url: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
if (getState().router.location.pathname !== url) {
|
if (getState().router.location.pathname !== url) {
|
||||||
dispatch(push(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
|
* Check if requested OR current url is landing page
|
||||||
*/
|
*/
|
||||||
export const isLandingPageUrl = ($url?: string, checkRoutes: boolean = false): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
export const isLandingPageUrl = (
|
||||||
|
$url?: string,
|
||||||
|
checkRoutes: boolean = false
|
||||||
|
): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
let url: ?string = $url;
|
let url: ?string = $url;
|
||||||
if (typeof url !== 'string') {
|
if (typeof url !== 'string') {
|
||||||
url = getState().router.location.pathname;
|
url = getState().router.location.pathname;
|
||||||
}
|
}
|
||||||
if (checkRoutes) {
|
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) {
|
if (isLandingRoute) {
|
||||||
return true;
|
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 => {
|
export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
const isLandingPage = dispatch(isLandingPageUrl());
|
const isLandingPage = dispatch(isLandingPageUrl());
|
||||||
if (!isLandingPage) {
|
if (!isLandingPage) {
|
||||||
@ -320,61 +374,76 @@ export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Go to given device settings page
|
* Go to given device settings page
|
||||||
*/
|
*/
|
||||||
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
|
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (
|
||||||
|
dispatch: Dispatch
|
||||||
|
): void => {
|
||||||
if (device.features) {
|
if (device.features) {
|
||||||
const devUrl: string = `${device.features.device_id}${device.instance ? `:${device.instance}` : ''}`;
|
const devUrl: string = `${device.features.device_id}${
|
||||||
|
device.instance ? `:${device.instance}` : ''
|
||||||
|
}`;
|
||||||
dispatch(goto(`/device/${devUrl}/settings`));
|
dispatch(goto(`/device/${devUrl}/settings`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Go to UpdateBridge page
|
* Go to UpdateBridge page
|
||||||
*/
|
*/
|
||||||
export const gotoBridgeUpdate = (): ThunkAction => (dispatch: Dispatch): void => {
|
export const gotoBridgeUpdate = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
dispatch(goto('/bridge'));
|
dispatch(goto('/bridge'));
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Go to UpdateFirmware page
|
* Go to UpdateFirmware page
|
||||||
* Called from App notification
|
* Called from App notification
|
||||||
*/
|
*/
|
||||||
export const gotoFirmwareUpdate = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const gotoFirmwareUpdate = (): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const { selectedDevice } = getState().wallet;
|
const { selectedDevice } = getState().wallet;
|
||||||
if (!selectedDevice || !selectedDevice.features) return;
|
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`));
|
dispatch(goto(`/device/${devUrl}/firmware-update`));
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Go to NoBackup page
|
* Go to NoBackup page
|
||||||
*/
|
*/
|
||||||
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const { selectedDevice } = getState().wallet;
|
const { selectedDevice } = getState().wallet;
|
||||||
if (!selectedDevice || !selectedDevice.features) return;
|
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`));
|
dispatch(goto(`/device/${devUrl}/backup`));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to redirect to initial url
|
* Try to redirect to initial url
|
||||||
*/
|
*/
|
||||||
export const setInitialUrl = (): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
export const setInitialUrl = (): PayloadAction<boolean> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): boolean => {
|
||||||
const { initialPathname } = getState().wallet;
|
const { initialPathname } = getState().wallet;
|
||||||
if (typeof initialPathname === 'string' && !dispatch(isLandingPageUrl(initialPathname, true))) {
|
if (typeof initialPathname === 'string' && !dispatch(isLandingPageUrl(initialPathname, true))) {
|
||||||
const valid = dispatch(getValidUrl({
|
const valid = dispatch(
|
||||||
type: LOCATION_CHANGE,
|
getValidUrl({
|
||||||
payload: {
|
type: LOCATION_CHANGE,
|
||||||
location: {
|
payload: {
|
||||||
pathname: initialPathname,
|
location: {
|
||||||
hash: '',
|
pathname: initialPathname,
|
||||||
search: '',
|
hash: '',
|
||||||
state: {},
|
search: '',
|
||||||
|
state: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
}));
|
);
|
||||||
|
|
||||||
if (valid === initialPathname) {
|
if (valid === initialPathname) {
|
||||||
// reset initial url
|
// reset initial url
|
||||||
|
@ -11,13 +11,7 @@ import * as reducerUtils from 'reducers/utils';
|
|||||||
import { getVersion } from 'utils/device';
|
import { getVersion } from 'utils/device';
|
||||||
import { initialState } from 'reducers/SelectedAccountReducer';
|
import { initialState } from 'reducers/SelectedAccountReducer';
|
||||||
|
|
||||||
import type {
|
import type { PayloadAction, Action, GetState, Dispatch, State } from 'flowtype';
|
||||||
PayloadAction,
|
|
||||||
Action,
|
|
||||||
GetState,
|
|
||||||
Dispatch,
|
|
||||||
State,
|
|
||||||
} from 'flowtype';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
State as SelectedAccountState,
|
State as SelectedAccountState,
|
||||||
@ -26,12 +20,14 @@ import type {
|
|||||||
ExceptionPage,
|
ExceptionPage,
|
||||||
} from 'reducers/SelectedAccountReducer';
|
} from 'reducers/SelectedAccountReducer';
|
||||||
|
|
||||||
export type SelectedAccountAction = {
|
export type SelectedAccountAction =
|
||||||
type: typeof ACCOUNT.DISPOSE,
|
| {
|
||||||
} | {
|
type: typeof ACCOUNT.DISPOSE,
|
||||||
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
}
|
||||||
payload: SelectedAccountState,
|
| {
|
||||||
};
|
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
||||||
|
payload: SelectedAccountState,
|
||||||
|
};
|
||||||
|
|
||||||
export const dispose = (): Action => ({
|
export const dispose = (): Action => ({
|
||||||
type: ACCOUNT.DISPOSE,
|
type: ACCOUNT.DISPOSE,
|
||||||
@ -68,11 +64,7 @@ const getExceptionPage = (state: State, selectedAccount: SelectedAccountState):
|
|||||||
// display loader instead of component body
|
// display loader instead of component body
|
||||||
const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?Loader => {
|
const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?Loader => {
|
||||||
const device = state.wallet.selectedDevice;
|
const device = state.wallet.selectedDevice;
|
||||||
const {
|
const { account, discovery, network } = selectedAccount;
|
||||||
account,
|
|
||||||
discovery,
|
|
||||||
network,
|
|
||||||
} = selectedAccount;
|
|
||||||
|
|
||||||
if (!device || !device.state) {
|
if (!device || !device.state) {
|
||||||
return {
|
return {
|
||||||
@ -89,7 +81,6 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState):
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (account) return null;
|
if (account) return null;
|
||||||
// account not found (yet). checking why...
|
// 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
|
// 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 device = state.wallet.selectedDevice;
|
||||||
const { account, network, discovery } = selectedAccount;
|
const { account, network, discovery } = selectedAccount;
|
||||||
if (!device || !network) return null;
|
if (!device || !network) return null;
|
||||||
@ -190,16 +184,21 @@ const actions = [
|
|||||||
...Object.values(BLOCKCHAIN).filter(v => typeof v === 'string'),
|
...Object.values(BLOCKCHAIN).filter(v => typeof v === 'string'),
|
||||||
WALLET.SET_SELECTED_DEVICE,
|
WALLET.SET_SELECTED_DEVICE,
|
||||||
WALLET.UPDATE_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(DISCOVERY).filter(v => typeof v === 'string'),
|
||||||
...Object.values(TOKEN).filter(v => typeof v === 'string'),
|
...Object.values(TOKEN).filter(v => typeof v === 'string'),
|
||||||
...Object.values(PENDING).filter(v => typeof v === 'string'),
|
...Object.values(PENDING).filter(v => typeof v === 'string'),
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from WalletService
|
* Called from WalletService
|
||||||
*/
|
*/
|
||||||
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): boolean => {
|
||||||
// ignore not listed actions
|
// ignore not listed actions
|
||||||
if (actions.indexOf(action.type) < 0) return false;
|
if (actions.indexOf(action.type) < 0) return false;
|
||||||
const state: State = getState();
|
const state: State = getState();
|
||||||
@ -238,12 +237,22 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
|
|||||||
newState.notification = notification;
|
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
|
// check if newState is different than previous state
|
||||||
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, {
|
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, {
|
||||||
account: ['balance', 'nonce'],
|
account: ['balance', 'nonce'],
|
||||||
discovery: ['accountIndex', 'interrupted', 'completed', 'waitingForBlockchain', 'waitingForDevice'],
|
discovery: [
|
||||||
|
'accountIndex',
|
||||||
|
'interrupted',
|
||||||
|
'completed',
|
||||||
|
'waitingForBlockchain',
|
||||||
|
'waitingForDevice',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (stateChanged) {
|
if (stateChanged) {
|
||||||
|
@ -4,33 +4,30 @@ import * as SEND from 'actions/constants/send';
|
|||||||
import * as WEB3 from 'actions/constants/web3';
|
import * as WEB3 from 'actions/constants/web3';
|
||||||
import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
||||||
|
|
||||||
import type {
|
import type { Dispatch, GetState, State as ReducersState, Action, ThunkAction } from 'flowtype';
|
||||||
Dispatch,
|
|
||||||
GetState,
|
|
||||||
State as ReducersState,
|
|
||||||
Action,
|
|
||||||
ThunkAction,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { State as EthereumState } from 'reducers/SendFormEthereumReducer';
|
import type { State as EthereumState } from 'reducers/SendFormEthereumReducer';
|
||||||
import type { State as RippleState } from 'reducers/SendFormRippleReducer';
|
import type { State as RippleState } from 'reducers/SendFormRippleReducer';
|
||||||
|
|
||||||
import * as EthereumSendFormActions from './ethereum/SendFormActions';
|
import * as EthereumSendFormActions from './ethereum/SendFormActions';
|
||||||
import * as RippleSendFormActions from './ripple/SendFormActions';
|
import * as RippleSendFormActions from './ripple/SendFormActions';
|
||||||
|
|
||||||
export type SendFormAction = {
|
export type SendFormAction =
|
||||||
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
|
| {
|
||||||
networkType: 'ethereum',
|
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
|
||||||
state: EthereumState,
|
networkType: 'ethereum',
|
||||||
} | {
|
state: EthereumState,
|
||||||
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
|
}
|
||||||
networkType: 'ripple',
|
| {
|
||||||
state: RippleState,
|
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
|
||||||
} | {
|
networkType: 'ripple',
|
||||||
type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR,
|
state: RippleState,
|
||||||
} | {
|
}
|
||||||
type: typeof SEND.TX_COMPLETE,
|
| {
|
||||||
};
|
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
|
// list of all actions which has influence on "sendForm" reducer
|
||||||
// other actions will be ignored
|
// other actions will be ignored
|
||||||
@ -42,9 +39,12 @@ const actions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from WalletService
|
* Called from WalletService
|
||||||
*/
|
*/
|
||||||
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
// ignore not listed actions
|
// ignore not listed actions
|
||||||
if (actions.indexOf(action.type) < 0) return;
|
if (actions.indexOf(action.type) < 0) return;
|
||||||
|
|
||||||
@ -62,6 +62,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
case 'ripple':
|
case 'ripple':
|
||||||
dispatch(RippleSendFormActions.observe(prevState, action));
|
dispatch(RippleSendFormActions.observe(prevState, action));
|
||||||
break;
|
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 EthereumSendFormState } from 'reducers/SendFormEthereumReducer';
|
||||||
import type { State as RippleSendFormState } from 'reducers/SendFormRippleReducer';
|
import type { State as RippleSendFormState } from 'reducers/SendFormRippleReducer';
|
||||||
import type {
|
import type { ThunkAction, PayloadAction, GetState, Dispatch } from 'flowtype';
|
||||||
ThunkAction,
|
|
||||||
PayloadAction,
|
|
||||||
GetState,
|
|
||||||
Dispatch,
|
|
||||||
} from 'flowtype';
|
|
||||||
|
|
||||||
const TYPE: 'session' = 'session';
|
const TYPE: 'session' = 'session';
|
||||||
const { STORAGE_PATH } = storageUtils;
|
const { STORAGE_PATH } = storageUtils;
|
||||||
@ -20,7 +15,10 @@ const getTxDraftKey = (getState: GetState): string => {
|
|||||||
return `${KEY_TX_DRAFT}${pathname}`;
|
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;
|
const state = getState().sendFormEthereum;
|
||||||
if (state.untouched) return;
|
if (state.untouched) return;
|
||||||
|
|
||||||
@ -28,7 +26,10 @@ export const saveDraftTransaction = (): ThunkAction => (dispatch: Dispatch, getS
|
|||||||
storageUtils.set(TYPE, key, JSON.stringify(state));
|
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 key = getTxDraftKey(getState);
|
||||||
const value: ?string = storageUtils.get(TYPE, key);
|
const value: ?string = storageUtils.get(TYPE, key);
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
@ -53,7 +54,10 @@ export const loadEthereumDraftTransaction = (): PayloadAction<?EthereumSendFormS
|
|||||||
return state;
|
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 key = getTxDraftKey(getState);
|
||||||
const value: ?string = storageUtils.get(TYPE, key);
|
const value: ?string = storageUtils.get(TYPE, key);
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
|
@ -1,41 +1,45 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import type {
|
import type { GetState, Dispatch, ThunkAction, AsyncAction } from 'flowtype';
|
||||||
GetState, Dispatch, ThunkAction, AsyncAction,
|
|
||||||
} from 'flowtype';
|
|
||||||
import { validateAddress } from 'utils/ethUtils';
|
import { validateAddress } from 'utils/ethUtils';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import * as SIGN_VERIFY from './constants/signVerify';
|
import * as SIGN_VERIFY from './constants/signVerify';
|
||||||
|
|
||||||
export type SignVerifyAction = {
|
export type SignVerifyAction =
|
||||||
type: typeof SIGN_VERIFY.SIGN_SUCCESS,
|
| {
|
||||||
signSignature: string
|
type: typeof SIGN_VERIFY.SIGN_SUCCESS,
|
||||||
} | {
|
signSignature: string,
|
||||||
type: typeof SIGN_VERIFY.CLEAR_SIGN,
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof SIGN_VERIFY.CLEAR_VERIFY,
|
type: typeof SIGN_VERIFY.CLEAR_SIGN,
|
||||||
} | {
|
}
|
||||||
type: typeof SIGN_VERIFY.INPUT_CHANGE,
|
| {
|
||||||
inputName: string,
|
type: typeof SIGN_VERIFY.CLEAR_VERIFY,
|
||||||
value: string
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof SIGN_VERIFY.TOUCH,
|
type: typeof SIGN_VERIFY.INPUT_CHANGE,
|
||||||
inputName: string,
|
inputName: string,
|
||||||
} | {
|
value: string,
|
||||||
type: typeof SIGN_VERIFY.ERROR,
|
}
|
||||||
inputName: string,
|
| {
|
||||||
message: ?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,
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: typeof SIGN_VERIFY.ERROR,
|
||||||
|
inputName: string,
|
||||||
|
message: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
const sign = (
|
const sign = (path: Array<number>, message: string, hex: boolean = false): AsyncAction => async (
|
||||||
path: Array<number>,
|
dispatch: Dispatch,
|
||||||
message: string,
|
getState: GetState
|
||||||
hex: boolean = false,
|
): Promise<void> => {
|
||||||
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
|
||||||
const selected = getState().wallet.selectedDevice;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
|
|
||||||
@ -73,7 +77,7 @@ const verify = (
|
|||||||
address: string,
|
address: string,
|
||||||
message: string,
|
message: string,
|
||||||
signature: string,
|
signature: string,
|
||||||
hex: boolean = false,
|
hex: boolean = false
|
||||||
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const selected = getState().wallet.selectedDevice;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
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({
|
dispatch({
|
||||||
type: SIGN_VERIFY.INPUT_CHANGE,
|
type: SIGN_VERIFY.INPUT_CHANGE,
|
||||||
inputName,
|
inputName,
|
||||||
@ -162,4 +168,4 @@ export default {
|
|||||||
clearSign,
|
clearSign,
|
||||||
clearVerify,
|
clearVerify,
|
||||||
inputChange,
|
inputChange,
|
||||||
};
|
};
|
||||||
|
@ -2,19 +2,20 @@
|
|||||||
import * as SUMMARY from 'actions/constants/summary';
|
import * as SUMMARY from 'actions/constants/summary';
|
||||||
import { initialState } from 'reducers/SummaryReducer';
|
import { initialState } from 'reducers/SummaryReducer';
|
||||||
|
|
||||||
import type {
|
import type { ThunkAction, Action, Dispatch } from 'flowtype';
|
||||||
ThunkAction, Action, Dispatch,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { State } from 'reducers/SummaryReducer';
|
import type { State } from 'reducers/SummaryReducer';
|
||||||
|
|
||||||
export type SummaryAction = {
|
export type SummaryAction =
|
||||||
type: typeof SUMMARY.INIT,
|
| {
|
||||||
state: State
|
type: typeof SUMMARY.INIT,
|
||||||
} | {
|
state: State,
|
||||||
type: typeof SUMMARY.DISPOSE,
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof SUMMARY.DETAILS_TOGGLE
|
type: typeof SUMMARY.DISPOSE,
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: typeof SUMMARY.DETAILS_TOGGLE,
|
||||||
|
};
|
||||||
|
|
||||||
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
const state: State = {
|
const state: State = {
|
||||||
|
@ -1,41 +1,47 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import * as TOKEN from 'actions/constants/token';
|
import * as TOKEN from 'actions/constants/token';
|
||||||
|
|
||||||
import type {
|
import type { GetState, AsyncAction, Action, Dispatch } from 'flowtype';
|
||||||
GetState, AsyncAction, Action, Dispatch,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { State, Token } from 'reducers/TokensReducer';
|
import type { State, Token } from 'reducers/TokensReducer';
|
||||||
import type { Account } from 'reducers/AccountsReducer';
|
import type { Account } from 'reducers/AccountsReducer';
|
||||||
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
||||||
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
||||||
|
|
||||||
export type TokenAction = {
|
export type TokenAction =
|
||||||
type: typeof TOKEN.FROM_STORAGE,
|
| {
|
||||||
payload: State
|
type: typeof TOKEN.FROM_STORAGE,
|
||||||
} | {
|
payload: State,
|
||||||
type: typeof TOKEN.ADD,
|
}
|
||||||
payload: Token
|
| {
|
||||||
} | {
|
type: typeof TOKEN.ADD,
|
||||||
type: typeof TOKEN.REMOVE,
|
payload: Token,
|
||||||
token: Token
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof TOKEN.SET_BALANCE,
|
type: typeof TOKEN.REMOVE,
|
||||||
payload: State
|
token: Token,
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: typeof TOKEN.SET_BALANCE,
|
||||||
|
payload: State,
|
||||||
|
};
|
||||||
|
|
||||||
// action from component <reactSelect>
|
// 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;
|
let input = $input;
|
||||||
if (input.length < 1) input = '0x';
|
if (input.length < 1) input = '0x';
|
||||||
|
|
||||||
const tokens = getState().localStorage.tokens[network];
|
const tokens = getState().localStorage.tokens[network];
|
||||||
const value = input.toLowerCase();
|
const value = input.toLowerCase();
|
||||||
const result = tokens.filter(t => t.symbol.toLowerCase().indexOf(value) >= 0
|
const result = tokens.filter(
|
||||||
|| t.address.toLowerCase().indexOf(value) >= 0
|
t =>
|
||||||
|| t.name.toLowerCase().indexOf(value) >= 0);
|
t.symbol.toLowerCase().indexOf(value) >= 0 ||
|
||||||
|
t.address.toLowerCase().indexOf(value) >= 0 ||
|
||||||
|
t.name.toLowerCase().indexOf(value) >= 0
|
||||||
|
);
|
||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
// TODO: Temporary fix for async select
|
// TODO: Temporary fix for async select
|
||||||
@ -52,23 +58,33 @@ export const load = ($input: string, network: string): AsyncAction => async (dis
|
|||||||
return null;
|
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 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) {
|
if (token) {
|
||||||
const others = newState.filter(t => t !== token);
|
const others = newState.filter(t => t !== token);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: TOKEN.SET_BALANCE,
|
type: TOKEN.SET_BALANCE,
|
||||||
payload: others.concat([{
|
payload: others.concat([
|
||||||
...token,
|
{
|
||||||
loaded: true,
|
...token,
|
||||||
balance,
|
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 = {
|
const tkn: Token = {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
deviceState: account.deviceState,
|
deviceState: account.deviceState,
|
||||||
@ -93,4 +109,4 @@ export const add = (token: NetworkToken, account: Account): AsyncAction => async
|
|||||||
export const remove = (token: Token): Action => ({
|
export const remove = (token: Token): Action => ({
|
||||||
type: TOKEN.REMOVE,
|
type: TOKEN.REMOVE,
|
||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import TrezorConnect, {
|
import TrezorConnect, {
|
||||||
DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT,
|
DEVICE,
|
||||||
|
DEVICE_EVENT,
|
||||||
|
UI_EVENT,
|
||||||
|
TRANSPORT_EVENT,
|
||||||
|
BLOCKCHAIN_EVENT,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
import { CONTEXT_NONE } from 'actions/constants/modal';
|
import { CONTEXT_NONE } from 'actions/constants/modal';
|
||||||
import urlConstants from 'constants/urls';
|
import urlConstants from 'constants/urls';
|
||||||
@ -31,96 +35,127 @@ import type {
|
|||||||
TrezorDevice,
|
TrezorDevice,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
|
export type TrezorConnectAction =
|
||||||
export type TrezorConnectAction = {
|
| {
|
||||||
type: typeof CONNECT.INITIALIZATION_ERROR,
|
type: typeof CONNECT.INITIALIZATION_ERROR,
|
||||||
error: string
|
error: string,
|
||||||
} | {
|
}
|
||||||
type: typeof CONNECT.NETWORK_CHANGED,
|
| {
|
||||||
payload: {
|
type: typeof CONNECT.NETWORK_CHANGED,
|
||||||
network: string
|
payload: {
|
||||||
}
|
network: string,
|
||||||
} | {
|
},
|
||||||
type: typeof CONNECT.AUTH_DEVICE,
|
}
|
||||||
device: TrezorDevice,
|
| {
|
||||||
state: string
|
type: typeof CONNECT.AUTH_DEVICE,
|
||||||
} | {
|
device: TrezorDevice,
|
||||||
type: typeof CONNECT.DUPLICATE,
|
state: string,
|
||||||
device: TrezorDevice
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof CONNECT.REMEMBER_REQUEST,
|
type: typeof CONNECT.DUPLICATE,
|
||||||
device: TrezorDevice,
|
device: TrezorDevice,
|
||||||
instances: Array<TrezorDevice>
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof CONNECT.DISCONNECT_REQUEST,
|
type: typeof CONNECT.REMEMBER_REQUEST,
|
||||||
device: TrezorDevice
|
device: TrezorDevice,
|
||||||
} | {
|
instances: Array<TrezorDevice>,
|
||||||
type: typeof CONNECT.FORGET_REQUEST,
|
}
|
||||||
device: TrezorDevice
|
| {
|
||||||
} | {
|
type: typeof CONNECT.DISCONNECT_REQUEST,
|
||||||
type: typeof CONNECT.FORGET,
|
device: TrezorDevice,
|
||||||
device: TrezorDevice
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof CONNECT.FORGET_SINGLE | typeof CONNECT.FORGET_SILENT,
|
type: typeof CONNECT.FORGET_REQUEST,
|
||||||
device: TrezorDevice
|
device: TrezorDevice,
|
||||||
} | {
|
}
|
||||||
type: typeof CONNECT.REMEMBER,
|
| {
|
||||||
device: TrezorDevice
|
type: typeof CONNECT.FORGET,
|
||||||
} | {
|
device: TrezorDevice,
|
||||||
type: typeof CONNECT.TRY_TO_DUPLICATE,
|
}
|
||||||
device: TrezorDevice
|
| {
|
||||||
} | {
|
type: typeof CONNECT.FORGET_SINGLE | typeof CONNECT.FORGET_SILENT,
|
||||||
type: typeof CONNECT.DEVICE_FROM_STORAGE,
|
device: TrezorDevice,
|
||||||
payload: Array<TrezorDevice>
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof CONNECT.START_ACQUIRING | typeof CONNECT.STOP_ACQUIRING,
|
type: typeof CONNECT.REMEMBER,
|
||||||
} | {
|
device: TrezorDevice,
|
||||||
type: typeof CONNECT.REQUEST_WALLET_TYPE,
|
}
|
||||||
device: TrezorDevice
|
| {
|
||||||
} | {
|
type: typeof CONNECT.TRY_TO_DUPLICATE,
|
||||||
type: typeof CONNECT.RECEIVE_WALLET_TYPE | typeof CONNECT.UPDATE_WALLET_TYPE,
|
device: TrezorDevice,
|
||||||
device: TrezorDevice,
|
}
|
||||||
hidden: boolean,
|
| {
|
||||||
};
|
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;
|
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
|
// set listeners
|
||||||
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
|
TrezorConnect.on(
|
||||||
// post event to reducers
|
DEVICE_EVENT,
|
||||||
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
|
(event: DeviceMessage): void => {
|
||||||
dispatch({
|
// post event to reducers
|
||||||
type,
|
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
device: event.payload,
|
dispatch({
|
||||||
});
|
type,
|
||||||
});
|
device: event.payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
|
TrezorConnect.on(
|
||||||
// post event to reducers
|
UI_EVENT,
|
||||||
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
|
(event: UiMessage): void => {
|
||||||
dispatch({
|
// post event to reducers
|
||||||
type,
|
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
payload: event.payload,
|
dispatch({
|
||||||
});
|
type,
|
||||||
});
|
payload: event.payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
|
TrezorConnect.on(
|
||||||
// post event to reducers
|
TRANSPORT_EVENT,
|
||||||
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
|
(event: TransportMessage): void => {
|
||||||
dispatch({
|
// post event to reducers
|
||||||
type,
|
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
payload: event.payload,
|
dispatch({
|
||||||
});
|
type,
|
||||||
});
|
payload: event.payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainEvent): void => {
|
TrezorConnect.on(
|
||||||
dispatch(event);
|
BLOCKCHAIN_EVENT,
|
||||||
});
|
(event: BlockchainEvent): void => {
|
||||||
|
dispatch(event);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (buildUtils.isDev()) {
|
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.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://localhost:8088/'; // eslint-disable-line no-underscore-dangle
|
||||||
window.TrezorConnect = TrezorConnect;
|
window.TrezorConnect = TrezorConnect;
|
||||||
}
|
}
|
||||||
@ -131,7 +166,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
debug: false,
|
debug: false,
|
||||||
popup: false,
|
popup: false,
|
||||||
webusb: true,
|
webusb: true,
|
||||||
pendingTransportEvent: (getState().devices.length < 1),
|
pendingTransportEvent: getState().devices.length < 1,
|
||||||
manifest: {
|
manifest: {
|
||||||
email: 'info@trezor.io',
|
email: 'info@trezor.io',
|
||||||
appUrl: urlConstants.NEXT_WALLET,
|
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;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
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 (!isDeviceReady) return;
|
||||||
|
|
||||||
if (selected.features && selected.features.passphrase_protection) {
|
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;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
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 (!isDeviceReady) return;
|
||||||
|
|
||||||
const response = await TrezorConnect.getDeviceState({
|
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) {
|
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) {
|
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) {
|
if (!isSelected && getState().modal.context !== CONTEXT_NONE) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CONNECT.FORGET_SILENT,
|
type: CONNECT.FORGET_SILENT,
|
||||||
@ -259,8 +323,7 @@ export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function reload(): AsyncAction {
|
export function reload(): AsyncAction {
|
||||||
return async (): Promise<void> => {
|
return async (): Promise<void> => {};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function acquire(): AsyncAction {
|
export function acquire(): AsyncAction {
|
||||||
@ -314,7 +377,10 @@ export const forget = (device: TrezorDevice): Action => ({
|
|||||||
device,
|
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 instance: number = getDuplicateInstanceNumber(getState().devices, device);
|
||||||
const extended: Object = { instance };
|
const extended: Object = { instance };
|
||||||
dispatch({
|
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({
|
dispatch({
|
||||||
type: CONNECT.REQUEST_WALLET_TYPE,
|
type: CONNECT.REQUEST_WALLET_TYPE,
|
||||||
device,
|
device,
|
||||||
|
@ -7,27 +7,28 @@ import { toHex } from 'web3-utils'; // eslint-disable-line import/no-extraneous-
|
|||||||
import { initWeb3 } from 'actions/Web3Actions';
|
import { initWeb3 } from 'actions/Web3Actions';
|
||||||
import * as ethUtils from 'utils/ethUtils';
|
import * as ethUtils from 'utils/ethUtils';
|
||||||
|
|
||||||
import type {
|
import type { Dispatch, PromiseAction } from 'flowtype';
|
||||||
Dispatch,
|
|
||||||
PromiseAction,
|
|
||||||
} from 'flowtype';
|
|
||||||
|
|
||||||
import type { EthereumTransaction } from 'trezor-connect';
|
import type { EthereumTransaction } from 'trezor-connect';
|
||||||
import type { Token } from 'reducers/TokensReducer';
|
import type { Token } from 'reducers/TokensReducer';
|
||||||
|
|
||||||
type EthereumTxRequest = {
|
type EthereumTxRequest = {
|
||||||
network: string;
|
network: string,
|
||||||
token: ?Token;
|
token: ?Token,
|
||||||
from: string;
|
from: string,
|
||||||
to: string;
|
to: string,
|
||||||
amount: string;
|
amount: string,
|
||||||
data: string;
|
data: string,
|
||||||
gasLimit: string;
|
gasLimit: string,
|
||||||
gasPrice: string;
|
gasPrice: string,
|
||||||
nonce: number;
|
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 instance = await dispatch(initWeb3(tx.network));
|
||||||
const { token } = tx;
|
const { token } = tx;
|
||||||
let data: string = ethUtils.sanitizeHex(tx.data);
|
let data: string = ethUtils.sanitizeHex(tx.data);
|
||||||
@ -37,7 +38,9 @@ export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<Ethereum
|
|||||||
// smart contract transaction
|
// smart contract transaction
|
||||||
const contract = instance.erc20.clone();
|
const contract = instance.erc20.clone();
|
||||||
contract.options.address = token.address;
|
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();
|
data = instance.erc20.methods.transfer(to, tokenAmount).encodeABI();
|
||||||
value = '0x00';
|
value = '0x00';
|
||||||
to = token.address;
|
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);
|
const ethTx = new EthereumjsTx(tx);
|
||||||
return `0x${ethTx.serialize().toString('hex')}`;
|
return `0x${ethTx.serialize().toString('hex')}`;
|
||||||
};
|
};
|
||||||
|
@ -19,34 +19,46 @@ import type {
|
|||||||
State,
|
State,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
export type WalletAction = {
|
export type WalletAction =
|
||||||
type: typeof WALLET.SET_INITIAL_URL,
|
| {
|
||||||
state?: RouterLocationState,
|
type: typeof WALLET.SET_INITIAL_URL,
|
||||||
pathname?: string
|
state?: RouterLocationState,
|
||||||
} | {
|
pathname?: string,
|
||||||
type: typeof WALLET.TOGGLE_DEVICE_DROPDOWN,
|
}
|
||||||
opened: boolean
|
| {
|
||||||
} | {
|
type: typeof WALLET.TOGGLE_DEVICE_DROPDOWN,
|
||||||
type: typeof WALLET.ONLINE_STATUS,
|
opened: boolean,
|
||||||
online: boolean
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof WALLET.SET_SELECTED_DEVICE,
|
type: typeof WALLET.ONLINE_STATUS,
|
||||||
device: ?TrezorDevice
|
online: boolean,
|
||||||
} | {
|
}
|
||||||
type: typeof WALLET.UPDATE_SELECTED_DEVICE,
|
| {
|
||||||
device: TrezorDevice
|
type: typeof WALLET.SET_SELECTED_DEVICE,
|
||||||
} | {
|
device: ?TrezorDevice,
|
||||||
type: typeof WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
|
}
|
||||||
devices: Array<TrezorDevice>
|
| {
|
||||||
} | {
|
type: typeof WALLET.UPDATE_SELECTED_DEVICE,
|
||||||
type: typeof WALLET.SHOW_BETA_DISCLAIMER | typeof WALLET.HIDE_BETA_DISCLAIMER | typeof WALLET.SET_FIRST_LOCATION_CHANGE,
|
device: TrezorDevice,
|
||||||
} | {
|
}
|
||||||
type: typeof WALLET.TOGGLE_SIDEBAR,
|
| {
|
||||||
} | {
|
type: typeof WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA,
|
||||||
type: typeof WALLET.SET_LANGUAGE,
|
devices: Array<TrezorDevice>,
|
||||||
locale: string,
|
}
|
||||||
messages: { [string]: string },
|
| {
|
||||||
}
|
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 => {
|
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
const updateOnlineStatus = () => {
|
const updateOnlineStatus = () => {
|
||||||
@ -75,7 +87,7 @@ export const toggleSidebar = (): WalletAction => ({
|
|||||||
export const fetchLocale = (locale: string): ThunkAction => (dispatch: Dispatch): void => {
|
export const fetchLocale = (locale: string): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
fetch(`/l10n/${locale}.json`)
|
fetch(`/l10n/${locale}.json`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then((messages) => {
|
.then(messages => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: WALLET.SET_LANGUAGE,
|
type: WALLET.SET_LANGUAGE,
|
||||||
locale,
|
locale,
|
||||||
@ -89,12 +101,18 @@ export const fetchLocale = (locale: string): ThunkAction => (dispatch: Dispatch)
|
|||||||
// all saved instances will be removed immediately inside DevicesReducer
|
// all saved instances will be removed immediately inside DevicesReducer
|
||||||
// This method will clear leftovers associated with removed instances from reducers.
|
// This method will clear leftovers associated with removed instances from reducers.
|
||||||
// (DiscoveryReducer, AccountReducer, TokensReducer)
|
// (DiscoveryReducer, AccountReducer, TokensReducer)
|
||||||
export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (dispatch: Dispatch): void => {
|
export const clearUnavailableDevicesData = (prevState: State, device: Device): ThunkAction => (
|
||||||
|
dispatch: Dispatch
|
||||||
|
): void => {
|
||||||
if (!device.features) return;
|
if (!device.features) return;
|
||||||
|
|
||||||
const affectedDevices = prevState.devices.filter(d => d.features && device.features
|
const affectedDevices = prevState.devices.filter(
|
||||||
&& d.features.device_id === device.features.device_id
|
d =>
|
||||||
&& d.features.passphrase_protection !== device.features.passphrase_protection);
|
d.features &&
|
||||||
|
device.features &&
|
||||||
|
d.features.device_id === device.features.device_id &&
|
||||||
|
d.features.passphrase_protection !== device.features.passphrase_protection
|
||||||
|
);
|
||||||
|
|
||||||
if (affectedDevices.length > 0) {
|
if (affectedDevices.length > 0) {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -114,15 +132,21 @@ const actions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from WalletService
|
* Called from WalletService
|
||||||
*/
|
*/
|
||||||
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): boolean => {
|
||||||
// ignore not listed actions
|
// ignore not listed actions
|
||||||
if (actions.indexOf(action.type) < 0) return false;
|
if (actions.indexOf(action.type) < 0) return false;
|
||||||
|
|
||||||
const state: State = getState();
|
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 device = reducerUtils.getSelectedDevice(state);
|
||||||
const selectedDeviceChanged = reducerUtils.observeChanges(state.wallet.selectedDevice, device);
|
const selectedDeviceChanged = reducerUtils.observeChanges(state.wallet.selectedDevice, device);
|
||||||
|
|
||||||
@ -142,4 +166,4 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -9,12 +9,7 @@ import * as WEB3 from 'actions/constants/web3';
|
|||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
import * as ethUtils from 'utils/ethUtils';
|
import * as ethUtils from 'utils/ethUtils';
|
||||||
|
|
||||||
import type {
|
import type { Dispatch, GetState, ThunkAction, PromiseAction } from 'flowtype';
|
||||||
Dispatch,
|
|
||||||
GetState,
|
|
||||||
ThunkAction,
|
|
||||||
PromiseAction,
|
|
||||||
} from 'flowtype';
|
|
||||||
|
|
||||||
import type { EthereumAccount } from 'trezor-connect';
|
import type { EthereumAccount } from 'trezor-connect';
|
||||||
import type { Account } from 'reducers/AccountsReducer';
|
import type { Account } from 'reducers/AccountsReducer';
|
||||||
@ -27,100 +22,114 @@ import * as AccountsActions from './AccountsActions';
|
|||||||
export type Web3UpdateBlockAction = {
|
export type Web3UpdateBlockAction = {
|
||||||
type: typeof WEB3.BLOCK_UPDATED,
|
type: typeof WEB3.BLOCK_UPDATED,
|
||||||
network: string,
|
network: string,
|
||||||
blockHash: string
|
blockHash: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Web3UpdateGasPriceAction = {
|
export type Web3UpdateGasPriceAction = {
|
||||||
type: typeof WEB3.GAS_PRICE_UPDATED,
|
type: typeof WEB3.GAS_PRICE_UPDATED,
|
||||||
network: string,
|
network: string,
|
||||||
gasPrice: string
|
gasPrice: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Web3Action = {
|
export type Web3Action =
|
||||||
type: typeof WEB3.READY,
|
| {
|
||||||
} | {
|
type: typeof WEB3.READY,
|
||||||
type: typeof WEB3.START,
|
}
|
||||||
} | {
|
| {
|
||||||
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
|
type: typeof WEB3.START,
|
||||||
instance: Web3Instance
|
}
|
||||||
} | Web3UpdateBlockAction
|
| {
|
||||||
| Web3UpdateGasPriceAction;
|
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) => {
|
export const initWeb3 = (
|
||||||
// check if requested web was initialized before
|
network: string,
|
||||||
const instance = getState().web3.find(w3 => w3.network === network);
|
urlIndex: number = 0
|
||||||
if (instance && instance.web3.currentProvider.connected) {
|
): PromiseAction<Web3Instance> => async (
|
||||||
resolve(instance);
|
dispatch: Dispatch,
|
||||||
return;
|
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
|
// requested web3 wasn't initialized or is disconnected
|
||||||
// initialize again
|
// initialize again
|
||||||
const { config, ERC20Abi } = getState().localStorage;
|
const { config, ERC20Abi } = getState().localStorage;
|
||||||
const networkData = config.networks.find(c => c.shortcut === network);
|
const networkData = config.networks.find(c => c.shortcut === network);
|
||||||
if (!networkData) {
|
if (!networkData) {
|
||||||
// network not found
|
// network not found
|
||||||
reject(new Error(`Network ${network} not found in application config.`));
|
reject(new Error(`Network ${network} not found in application config.`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get first url
|
// get first url
|
||||||
const url = networkData.web3[urlIndex];
|
const url = networkData.web3[urlIndex];
|
||||||
if (!url) {
|
if (!url) {
|
||||||
reject(new Error('Web3 backend is not responding'));
|
reject(new Error('Web3 backend is not responding'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
|
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
|
||||||
|
|
||||||
const onConnect = async () => {
|
const onConnect = async () => {
|
||||||
const latestBlock = await web3.eth.getBlockNumber();
|
const latestBlock = await web3.eth.getBlockNumber();
|
||||||
const gasPrice = await web3.eth.getGasPrice();
|
const gasPrice = await web3.eth.getGasPrice();
|
||||||
|
|
||||||
const newInstance = {
|
const newInstance = {
|
||||||
network,
|
network,
|
||||||
web3,
|
web3,
|
||||||
chainId: networkData.chainId,
|
chainId: networkData.chainId,
|
||||||
erc20: new web3.eth.Contract(ERC20Abi),
|
erc20: new web3.eth.Contract(ERC20Abi),
|
||||||
latestBlock,
|
latestBlock,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: WEB3.CREATE,
|
||||||
|
instance: newInstance,
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(newInstance);
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch({
|
const onEnd = async () => {
|
||||||
type: WEB3.CREATE,
|
web3.currentProvider.reset();
|
||||||
instance: newInstance,
|
const oldInstance = getState().web3.find(w3 => w3.network === network);
|
||||||
});
|
|
||||||
|
|
||||||
resolve(newInstance);
|
if (oldInstance && oldInstance.web3.currentProvider.connected) {
|
||||||
};
|
// backend disconnects
|
||||||
|
// dispatch({
|
||||||
const onEnd = async () => {
|
// type: 'WEB3.DISCONNECT',
|
||||||
web3.currentProvider.reset();
|
// network
|
||||||
const oldInstance = getState().web3.find(w3 => w3.network === network);
|
// });
|
||||||
|
} else {
|
||||||
if (oldInstance && oldInstance.web3.currentProvider.connected) {
|
// backend initialization error for given url, try next one
|
||||||
// backend disconnects
|
try {
|
||||||
// dispatch({
|
const otherWeb3 = await dispatch(initWeb3(network, urlIndex + 1));
|
||||||
// type: 'WEB3.DISCONNECT',
|
resolve(otherWeb3);
|
||||||
// network
|
} catch (error) {
|
||||||
// });
|
reject(error);
|
||||||
} 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('connect', onConnect);
|
||||||
web3.currentProvider.on('end', onEnd);
|
web3.currentProvider.on('end', onEnd);
|
||||||
web3.currentProvider.on('error', 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 instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const balance = await instance.web3.eth.getBalance(descriptor);
|
const balance = await instance.web3.eth.getBalance(descriptor);
|
||||||
const nonce = await instance.web3.eth.getTransactionCount(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 instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const pending = getState().pending.filter(p => p.network === 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);
|
const status = await instance.web3.eth.getTransaction(tx.hash);
|
||||||
if (!status) {
|
if (!status) {
|
||||||
dispatch({
|
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 instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const balance = await instance.web3.eth.getBalance(account.descriptor);
|
const balance = await instance.web3.eth.getBalance(account.descriptor);
|
||||||
const nonce = await instance.web3.eth.getTransactionCount(account.descriptor);
|
const nonce = await instance.web3.eth.getTransactionCount(account.descriptor);
|
||||||
dispatch(AccountsActions.update({
|
dispatch(
|
||||||
networkType: 'ethereum',
|
AccountsActions.update({
|
||||||
...account,
|
networkType: 'ethereum',
|
||||||
...newAccount,
|
...account,
|
||||||
nonce,
|
...newAccount,
|
||||||
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
nonce,
|
||||||
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
}));
|
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// update tokens for this account
|
// update tokens for this account
|
||||||
dispatch(updateAccountTokens(account));
|
dispatch(updateAccountTokens(account));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (
|
||||||
const tokens = getState().tokens.filter(t => t.network === account.network && t.ethAddress === account.descriptor);
|
dispatch: Dispatch,
|
||||||
tokens.forEach(async (token) => {
|
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 balance = await dispatch(getTokenBalance(token));
|
||||||
// const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
|
// const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
|
||||||
if (balance !== token.balance) {
|
if (balance !== token.balance) {
|
||||||
dispatch(TokenActions.setBalance(
|
dispatch(TokenActions.setBalance(token.address, token.ethAddress, balance));
|
||||||
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 instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const contract = instance.erc20.clone();
|
const contract = instance.erc20.clone();
|
||||||
contract.options.address = address;
|
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 instance = await dispatch(initWeb3(token.network));
|
||||||
const contract = instance.erc20.clone();
|
const contract = instance.erc20.clone();
|
||||||
contract.options.address = token.address;
|
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);
|
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);
|
const instance = getState().web3.find(w3 => w3.network === network);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei');
|
return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei');
|
||||||
@ -258,7 +285,9 @@ export const getCurrentGasPrice = (network: string): PromiseAction<string> => as
|
|||||||
return '0';
|
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 {
|
try {
|
||||||
const instance = await dispatch(initWeb3(network));
|
const instance = await dispatch(initWeb3(network));
|
||||||
const gasPrice = await instance.web3.eth.getGasPrice();
|
const gasPrice = await instance.web3.eth.getGasPrice();
|
||||||
@ -275,23 +304,32 @@ export const updateGasPrice = (network: string): PromiseAction<void> => async (d
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const estimateGasLimit = (
|
||||||
export const estimateGasLimit = (network: string, $options: EstimateGasOptions): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
|
network: string,
|
||||||
|
$options: EstimateGasOptions
|
||||||
|
): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
|
||||||
const instance = await dispatch(initWeb3(network));
|
const instance = await dispatch(initWeb3(network));
|
||||||
const data = ethUtils.sanitizeHex($options.data);
|
const data = ethUtils.sanitizeHex($options.data);
|
||||||
const options = {
|
const options = {
|
||||||
...$options,
|
...$options,
|
||||||
to: '0x0000000000000000000000000000000000000000',
|
to: '0x0000000000000000000000000000000000000000',
|
||||||
data,
|
data,
|
||||||
value: instance.web3.utils.toHex(EthereumjsUnits.convert($options.value || '0', 'ether', 'wei')),
|
value: instance.web3.utils.toHex(
|
||||||
gasPrice: instance.web3.utils.toHex(EthereumjsUnits.convert($options.gasPrice, 'gwei', 'wei')),
|
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);
|
const limit = await instance.web3.eth.estimateGas(options);
|
||||||
return limit.toString();
|
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
|
// check if Web3 was already initialized
|
||||||
const instance = getState().web3.find(w3 => w3.network === network);
|
const instance = getState().web3.find(w3 => w3.network === network);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
//regExp1 : string = '(.*)'
|
//regExp1 : string = '(.*)'
|
||||||
//regExp2 : '$1' = '$1'
|
//regExp2 : '$1' = '$1'
|
||||||
|
|
||||||
export const READY: 'connect__ready' = 'connect__ready';
|
export const READY: 'connect__ready' = 'connect__ready';
|
||||||
export const INITIALIZATION_ERROR: 'connect__init_error' = 'connect__init_error';
|
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 DEVICE_FROM_STORAGE: 'connect__device_from_storage' = 'connect__device_from_storage';
|
||||||
export const AUTH_DEVICE: 'connect__auth_device' = 'connect__auth_device';
|
export const AUTH_DEVICE: 'connect__auth_device' = 'connect__auth_device';
|
||||||
export const NETWORK_CHANGED: 'connect__network_changed' = 'connect__network_changed';
|
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 TRY_TO_DUPLICATE: 'connect__try_to_duplicate' = 'connect__try_to_duplicate';
|
||||||
export const DUPLICATE: 'connect__duplicate' = 'connect__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 START_ACQUIRING: 'connect__start_acquiring' = 'connect__start_acquiring';
|
||||||
export const STOP_ACQUIRING: 'connect__stop_acquiring' = 'connect__stop_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 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 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 */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const INIT: 'account__init' = 'account__init';
|
export const INIT: 'account__init' = 'account__init';
|
||||||
export const DISPOSE: 'account__dispose' = 'account__dispose';
|
export const DISPOSE: 'account__dispose' = 'account__dispose';
|
||||||
|
|
||||||
@ -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 SET_NONCE: 'account__set_nonce' = 'account__set_nonce';
|
||||||
export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage';
|
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 START_SUBSCRIBE: 'blockchain__start_subscribe' = 'blockchain__start_subscribe';
|
||||||
export const READY: 'blockchain__ready' = 'blockchain__ready';
|
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 */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const START: 'discovery__start' = 'discovery__start';
|
export const START: 'discovery__start' = 'discovery__start';
|
||||||
export const STOP: 'discovery__stop' = 'discovery__stop';
|
export const STOP: 'discovery__stop' = 'discovery__stop';
|
||||||
export const FIRMWARE_NOT_SUPPORTED: 'discovery__fw_not_supported' = 'discovery__fw_not_supported';
|
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 FIRMWARE_OUTDATED: 'discovery__fw_outdated' = 'discovery__fw_outdated';
|
||||||
export const COMPLETE: 'discovery__complete' = 'discovery__complete';
|
export const COMPLETE: 'discovery__complete' = 'discovery__complete';
|
||||||
export const WAITING_FOR_DEVICE: 'discovery__waiting_for_device' = 'discovery__waiting_for_device';
|
export const WAITING_FOR_DEVICE: 'discovery__waiting_for_device' = 'discovery__waiting_for_device';
|
||||||
export const WAITING_FOR_BLOCKCHAIN: 'discovery__waiting_for_blockchain' = 'discovery__waiting_for_blockchain';
|
export const WAITING_FOR_BLOCKCHAIN: 'discovery__waiting_for_blockchain' =
|
||||||
export const FROM_STORAGE: 'discovery__from_storage' = 'discovery__from_storage';
|
'discovery__waiting_for_blockchain';
|
||||||
|
export const FROM_STORAGE: 'discovery__from_storage' = 'discovery__from_storage';
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const SAVE: 'storage__save' = 'storage__save';
|
export const SAVE: 'storage__save' = 'storage__save';
|
||||||
export const READY: 'storage__ready' = 'storage__ready';
|
export const READY: 'storage__ready' = 'storage__ready';
|
||||||
export const ERROR: 'storage__error' = 'storage__error';
|
export const ERROR: 'storage__error' = 'storage__error';
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const OPEN: 'log__open' = 'log__open';
|
export const OPEN: 'log__open' = 'log__open';
|
||||||
export const CLOSE: 'log__close' = 'log__close';
|
export const CLOSE: 'log__close' = 'log__close';
|
||||||
export const ADD: 'log__add' = 'log__add';
|
export const ADD: 'log__add' = 'log__add';
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const ADD: 'notification__add' = 'notification__add';
|
export const ADD: 'notification__add' = 'notification__add';
|
||||||
export const CLOSE: 'notification__close' = 'notification__close';
|
export const CLOSE: 'notification__close' = 'notification__close';
|
||||||
export const REMOVE: 'account__remove' = 'account__remove';
|
export const REMOVE: 'account__remove' = 'account__remove';
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage';
|
export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage';
|
||||||
export const ADD: 'pending__add' = 'pending__add';
|
export const ADD: 'pending__add' = 'pending__add';
|
||||||
export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved';
|
export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved';
|
||||||
export const TX_REJECTED: 'pending__tx_rejected' = 'pending__tx_rejected';
|
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 */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const INIT: 'receive__init' = 'receive__init';
|
export const INIT: 'receive__init' = 'receive__init';
|
||||||
export const DISPOSE: 'receive__dispose' = 'receive__dispose';
|
export const DISPOSE: 'receive__dispose' = 'receive__dispose';
|
||||||
export const REQUEST_UNVERIFIED: 'receive__request_unverified' = 'receive__request_unverified';
|
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_COMPLETE: 'send__tx_complete' = 'send__tx_complete';
|
||||||
export const TX_ERROR: 'send__tx_error' = 'send__tx_error';
|
export const TX_ERROR: 'send__tx_error' = 'send__tx_error';
|
||||||
export const TOGGLE_ADVANCED: 'send__toggle_advanced' = 'send__toggle_advanced';
|
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 TOUCH: 'sign__verify__input__touch' = 'sign__verify__input__touch';
|
||||||
export const CLEAR_SIGN: 'sign__verify__sign__clear' = 'sign__verify__sign__clear';
|
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 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 */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const INIT: 'summary__init' = 'summary__init';
|
export const INIT: 'summary__init' = 'summary__init';
|
||||||
export const DISPOSE: 'summary__dispose' = 'summary__dispose';
|
export const DISPOSE: 'summary__dispose' = 'summary__dispose';
|
||||||
export const ADD_TOKEN: 'summary__add_token' = 'summary__add_token';
|
export const ADD_TOKEN: 'summary__add_token' = 'summary__add_token';
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const ADD: 'token__add' = 'token__add';
|
export const ADD: 'token__add' = 'token__add';
|
||||||
export const REMOVE: 'token__remove' = 'token__remove';
|
export const REMOVE: 'token__remove' = 'token__remove';
|
||||||
export const SET_BALANCE: 'token__set_balance' = 'token__set_balance';
|
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 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_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 ONLINE_STATUS: 'wallet__online_status' = 'wallet__online_status';
|
||||||
|
|
||||||
export const SET_SELECTED_DEVICE: 'wallet__set_selected_device' = 'wallet__set_selected_device';
|
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 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 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 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 */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
export const START: 'web3__start' = 'web3__start';
|
export const START: 'web3__start' = 'web3__start';
|
||||||
export const STOP: 'web3__stop' = 'web3__stop';
|
export const STOP: 'web3__stop' = 'web3__stop';
|
||||||
export const CREATE: 'web3__create' = 'web3__create';
|
export const CREATE: 'web3__create' = 'web3__create';
|
||||||
export const READY: 'web3__ready' = 'web3__ready';
|
export const READY: 'web3__ready' = 'web3__ready';
|
||||||
export const BLOCK_UPDATED: 'web3__block_updated' = 'web3__block_updated';
|
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 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 BigNumber from 'bignumber.js';
|
||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
|
||||||
import type {
|
import type { TrezorDevice, Dispatch, GetState, PromiseAction } from 'flowtype';
|
||||||
TrezorDevice,
|
|
||||||
Dispatch,
|
|
||||||
GetState,
|
|
||||||
PromiseAction,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { EthereumAccount, BlockchainNotification } from 'trezor-connect';
|
import type { EthereumAccount, BlockchainNotification } from 'trezor-connect';
|
||||||
import type { Token } from 'reducers/TokensReducer';
|
import type { Token } from 'reducers/TokensReducer';
|
||||||
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
||||||
import * as Web3Actions from 'actions/Web3Actions';
|
import * as Web3Actions from 'actions/Web3Actions';
|
||||||
import * as AccountsActions from 'actions/AccountsActions';
|
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
|
// get data from connect
|
||||||
const txs = await TrezorConnect.ethereumGetAccountInfo({
|
const txs = await TrezorConnect.ethereumGetAccountInfo({
|
||||||
account: {
|
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 {
|
try {
|
||||||
const gasPrice = await dispatch(Web3Actions.getCurrentGasPrice(network));
|
const gasPrice = await dispatch(Web3Actions.getCurrentGasPrice(network));
|
||||||
return gasPrice === '0' ? new BigNumber(defaultGasPrice) : new BigNumber(gasPrice);
|
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>> = [];
|
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
|
// 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)
|
// 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?
|
// TODO: should reject second call immediately?
|
||||||
@ -69,12 +80,14 @@ export const estimateGasLimit = (network: string, data: string, value: string, g
|
|||||||
await estimateProxy[0];
|
await estimateProxy[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const call = dispatch(Web3Actions.estimateGasLimit(network, {
|
const call = dispatch(
|
||||||
to: '',
|
Web3Actions.estimateGasLimit(network, {
|
||||||
data,
|
to: '',
|
||||||
value,
|
data,
|
||||||
gasPrice,
|
value,
|
||||||
}));
|
gasPrice,
|
||||||
|
})
|
||||||
|
);
|
||||||
// add current call to proxy
|
// add current call to proxy
|
||||||
estimateProxy.push(call);
|
estimateProxy.push(call);
|
||||||
// wait for result
|
// wait for result
|
||||||
@ -85,8 +98,13 @@ export const estimateGasLimit = (network: string, data: string, value: string, g
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const subscribe = (network: string): PromiseAction<void> => async (
|
||||||
const accounts: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.descriptor); // eslint-disable-line no-unused-vars
|
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({
|
const response = await TrezorConnect.blockchainSubscribe({
|
||||||
accounts,
|
accounts,
|
||||||
coin: network,
|
coin: network,
|
||||||
@ -96,7 +114,10 @@ export const subscribe = (network: string): PromiseAction<void> => async (dispat
|
|||||||
await dispatch(Web3Actions.initWeb3(network));
|
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,
|
// TODO: handle rollback,
|
||||||
// check latest saved transaction blockhash against blockhheight
|
// 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 { notification } = payload;
|
||||||
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
||||||
if (!account) return;
|
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));
|
dispatch(Web3Actions.disconnect(network));
|
||||||
};
|
};
|
||||||
|
@ -5,14 +5,7 @@ import EthereumjsUtil from 'ethereumjs-util';
|
|||||||
import * as DISCOVERY from 'actions/constants/discovery';
|
import * as DISCOVERY from 'actions/constants/discovery';
|
||||||
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
||||||
|
|
||||||
import type {
|
import type { PromiseAction, Dispatch, GetState, TrezorDevice, Network, Account } from 'flowtype';
|
||||||
PromiseAction,
|
|
||||||
Dispatch,
|
|
||||||
GetState,
|
|
||||||
TrezorDevice,
|
|
||||||
Network,
|
|
||||||
Account,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
import type { Discovery } from 'reducers/DiscoveryReducer';
|
||||||
|
|
||||||
export type DiscoveryStartAction = {
|
export type DiscoveryStartAction = {
|
||||||
@ -28,7 +21,10 @@ export type DiscoveryStartAction = {
|
|||||||
// first iteration
|
// first iteration
|
||||||
// generate public key for this account
|
// generate public key for this account
|
||||||
// start discovery process
|
// 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
|
// get xpub from TREZOR
|
||||||
const response = await TrezorConnect.getPublicKey({
|
const response = await TrezorConnect.getPublicKey({
|
||||||
device: {
|
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 { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
||||||
if (!network) throw new Error('Discovery network not found');
|
if (!network) throw new Error('Discovery network not found');
|
||||||
|
|
||||||
const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`);
|
const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`);
|
||||||
const path = discoveryProcess.basePath.concat(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);
|
const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress);
|
||||||
|
|
||||||
// TODO: check if address was created before
|
// 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 accountIsEmpty = account.transactions <= 0 && account.nonce <= 0 && account.balance === '0';
|
||||||
const empty = 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',
|
networkType: 'ethereum',
|
||||||
nonce: account.nonce,
|
nonce: account.nonce,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -36,9 +36,12 @@ const actions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from WalletService
|
* Called from WalletService
|
||||||
*/
|
*/
|
||||||
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
// ignore not listed actions
|
// ignore not listed actions
|
||||||
if (actions.indexOf(action.type) < 0) return;
|
if (actions.indexOf(action.type) < 0) return;
|
||||||
|
|
||||||
@ -68,14 +71,26 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
|
|
||||||
let shouldUpdate: boolean = false;
|
let shouldUpdate: boolean = false;
|
||||||
// check if "selectedAccount" reducer changed
|
// check if "selectedAccount" reducer changed
|
||||||
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
|
shouldUpdate = reducerUtils.observeChanges(
|
||||||
account: ['balance', 'nonce', 'tokens'],
|
prevState.selectedAccount,
|
||||||
});
|
currentState.selectedAccount,
|
||||||
if (shouldUpdate && currentState.sendFormEthereum.currency !== currentState.sendFormEthereum.networkSymbol) {
|
{
|
||||||
|
account: ['balance', 'nonce', 'tokens'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
shouldUpdate &&
|
||||||
|
currentState.sendFormEthereum.currency !== currentState.sendFormEthereum.networkSymbol
|
||||||
|
) {
|
||||||
// make sure that this token is added into account
|
// make sure that this token is added into account
|
||||||
const { account, tokens } = getState().selectedAccount;
|
const { account, tokens } = getState().selectedAccount;
|
||||||
if (!account) return;
|
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) {
|
if (!token) {
|
||||||
// token not found, re-init form
|
// token not found, re-init form
|
||||||
dispatch(init());
|
dispatch(init());
|
||||||
@ -85,7 +100,10 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
|
|
||||||
// check if "sendFormEthereum" reducer changed
|
// check if "sendFormEthereum" reducer changed
|
||||||
if (!shouldUpdate) {
|
if (!shouldUpdate) {
|
||||||
shouldUpdate = reducerUtils.observeChanges(prevState.sendFormEthereum, currentState.sendFormEthereum);
|
shouldUpdate = reducerUtils.observeChanges(
|
||||||
|
prevState.sendFormEthereum,
|
||||||
|
currentState.sendFormEthereum
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
@ -99,15 +117,15 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from "observe" action
|
* Called from "observe" action
|
||||||
* Initialize "sendFormEthereum" reducer data
|
* Initialize "sendFormEthereum" reducer data
|
||||||
* Get data either from session storage or "selectedAccount" reducer
|
* Get data either from session storage or "selectedAccount" reducer
|
||||||
*/
|
*/
|
||||||
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const init = (): AsyncAction => async (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): Promise<void> => {
|
||||||
} = getState().selectedAccount;
|
const { account, network } = getState().selectedAccount;
|
||||||
|
|
||||||
if (!account || !network) return;
|
if (!account || !network) return;
|
||||||
|
|
||||||
@ -122,10 +140,15 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
return;
|
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 gasLimit = network.defaultGasLimit.toString();
|
||||||
const feeLevels = ValidationActions.getFeeLevels(network.symbol, gasPrice, gasLimit);
|
const feeLevels = ValidationActions.getFeeLevels(network.symbol, gasPrice, gasLimit);
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
initialState.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.INIT,
|
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 => ({
|
export const toggleAdvanced = (): Action => ({
|
||||||
type: SEND.TOGGLE_ADVANCED,
|
type: SEND.TOGGLE_ADVANCED,
|
||||||
networkType: 'ethereum',
|
networkType: 'ethereum',
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI from "clear" button
|
* Called from UI from "clear" button
|
||||||
*/
|
*/
|
||||||
export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onClear = (): AsyncAction => async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
const { advanced } = getState().sendFormEthereum;
|
const { advanced } = getState().sendFormEthereum;
|
||||||
|
|
||||||
@ -164,10 +190,15 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
|
|||||||
// clear transaction draft from session storage
|
// clear transaction draft from session storage
|
||||||
dispatch(SessionStorageActions.clear());
|
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 gasLimit = network.defaultGasLimit.toString();
|
||||||
const feeLevels = ValidationActions.getFeeLevels(network.symbol, gasPrice, gasLimit);
|
const feeLevels = ValidationActions.getFeeLevels(network.symbol, gasPrice, gasLimit);
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
initialState.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CLEAR,
|
type: SEND.CLEAR,
|
||||||
@ -188,9 +219,12 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "address" field change
|
* Called from UI on "address" field change
|
||||||
*/
|
*/
|
||||||
export const onAddressChange = (address: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onAddressChange = (address: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -205,9 +239,12 @@ export const onAddressChange = (address: string): ThunkAction => (dispatch: Disp
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "amount" field change
|
* Called from UI on "amount" field change
|
||||||
*/
|
*/
|
||||||
export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onAmountChange = (amount: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state = getState().sendFormEthereum;
|
const state = getState().sendFormEthereum;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -223,22 +260,32 @@ export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispat
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "currency" selection change
|
* Called from UI on "currency" selection change
|
||||||
*/
|
*/
|
||||||
export const onCurrencyChange = (currency: { value: string, label: string }): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onCurrencyChange = (currency: { value: string, label: string }): ThunkAction => (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): void => {
|
||||||
} = getState().selectedAccount;
|
const { account, network } = getState().selectedAccount;
|
||||||
if (!account || !network) return;
|
if (!account || !network) return;
|
||||||
|
|
||||||
const state = getState().sendFormEthereum;
|
const state = getState().sendFormEthereum;
|
||||||
|
|
||||||
const isToken = currency.value !== state.networkSymbol;
|
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 feeLevels = ValidationActions.getFeeLevels(
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
|
network.symbol,
|
||||||
|
state.recommendedGasPrice,
|
||||||
|
gasLimit,
|
||||||
|
state.selectedFeeLevel
|
||||||
|
);
|
||||||
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
state.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
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 => {
|
export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const state = getState().sendFormEthereum;
|
const state = getState().sendFormEthereum;
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -271,9 +318,12 @@ export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "fee" selection change
|
* Called from UI on "fee" selection change
|
||||||
*/
|
*/
|
||||||
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state = getState().sendFormEthereum;
|
const state = getState().sendFormEthereum;
|
||||||
|
|
||||||
const isCustom = feeLevel.value === 'Custom';
|
const isCustom = feeLevel.value === 'Custom';
|
||||||
@ -292,7 +342,9 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch:
|
|||||||
} else {
|
} else {
|
||||||
// corner case: gas limit was changed by user OR by "estimateGasPrice" action
|
// corner case: gas limit was changed by user OR by "estimateGasPrice" action
|
||||||
// leave gasLimit as it is
|
// 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;
|
newGasPrice = feeLevel.gasPrice;
|
||||||
}
|
}
|
||||||
@ -311,18 +363,26 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI from "update recommended fees" button
|
* Called from UI from "update recommended fees" button
|
||||||
*/
|
*/
|
||||||
export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const updateFeeLevels = (): ThunkAction => (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): void => {
|
||||||
} = getState().selectedAccount;
|
const { account, network } = getState().selectedAccount;
|
||||||
if (!account || !network) return;
|
if (!account || !network) return;
|
||||||
|
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
const feeLevels = ValidationActions.getFeeLevels(network.symbol, state.recommendedGasPrice, state.gasLimit, state.selectedFeeLevel);
|
const feeLevels = ValidationActions.getFeeLevels(
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
|
network.symbol,
|
||||||
|
state.recommendedGasPrice,
|
||||||
|
state.gasLimit,
|
||||||
|
state.selectedFeeLevel
|
||||||
|
);
|
||||||
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
state.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -338,13 +398,17 @@ export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "gas price" field change
|
* Called from UI on "gas price" field change
|
||||||
*/
|
*/
|
||||||
export const onGasPriceChange = (gasPrice: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onGasPriceChange = (gasPrice: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
// switch to custom fee level
|
// switch to custom fee level
|
||||||
let newSelectedFeeLevel = state.selectedFeeLevel;
|
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({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -360,16 +424,27 @@ export const onGasPriceChange = (gasPrice: string): ThunkAction => (dispatch: Di
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "data" field change
|
* Called from UI on "data" field change
|
||||||
* OR from "estimateGasPrice" action
|
* OR from "estimateGasPrice" action
|
||||||
*/
|
*/
|
||||||
export const onGasLimitChange = (gasLimit: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onGasLimitChange = (gasLimit: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
// recalculate feeLevels with recommended gasPrice
|
// recalculate feeLevels with recommended gasPrice
|
||||||
const feeLevels = ValidationActions.getFeeLevels(network.symbol, state.recommendedGasPrice, gasLimit, state.selectedFeeLevel);
|
const feeLevels = ValidationActions.getFeeLevels(
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
|
network.symbol,
|
||||||
|
state.recommendedGasPrice,
|
||||||
|
gasLimit,
|
||||||
|
state.selectedFeeLevel
|
||||||
|
);
|
||||||
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
state.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -387,9 +462,12 @@ export const onGasLimitChange = (gasLimit: string): ThunkAction => (dispatch: Di
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "nonce" field change
|
* Called from UI on "nonce" field change
|
||||||
*/
|
*/
|
||||||
export const onNonceChange = (nonce: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onNonceChange = (nonce: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -404,9 +482,12 @@ export const onNonceChange = (nonce: string): ThunkAction => (dispatch: Dispatch
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "data" field change
|
* Called from UI on "data" field change
|
||||||
*/
|
*/
|
||||||
export const onDataChange = (data: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onDataChange = (data: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -423,13 +504,18 @@ export const onDataChange = (data: string): ThunkAction => (dispatch: Dispatch,
|
|||||||
dispatch(estimateGasPrice());
|
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 state: State = getState().sendFormEthereum;
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
|
|
||||||
const isToken = state.currency !== state.networkSymbol;
|
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({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -445,11 +531,14 @@ export const setDefaultGasLimit = (): ThunkAction => (dispatch: Dispatch, getSta
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal method
|
* Internal method
|
||||||
* Called from "onDataChange" action
|
* Called from "onDataChange" action
|
||||||
* try to asynchronously download data from backend
|
* try to asynchronously download data from backend
|
||||||
*/
|
*/
|
||||||
const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
const estimateGasPrice = (): AsyncAction => async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
if (!network) {
|
if (!network) {
|
||||||
@ -461,7 +550,11 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
|
|||||||
const requestedData = state.data;
|
const requestedData = state.data;
|
||||||
if (!ethUtils.isHex(requestedData)) {
|
if (!ethUtils.isHex(requestedData)) {
|
||||||
// stop "calculatingGasLimit" process
|
// stop "calculatingGasLimit" process
|
||||||
dispatch(onGasLimitChange(requestedData.length > 0 ? state.gasLimit : network.defaultGasLimit.toString()));
|
dispatch(
|
||||||
|
onGasLimitChange(
|
||||||
|
requestedData.length > 0 ? state.gasLimit : network.defaultGasLimit.toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,7 +564,14 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
|
|||||||
return;
|
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
|
// double check "data" field
|
||||||
// possible race condition when data changed before backend respond
|
// 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
|
* Called from UI from "send" button
|
||||||
*/
|
*/
|
||||||
export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onSend = (): AsyncAction => async (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): Promise<void> => {
|
||||||
pending,
|
const { account, network, pending } = getState().selectedAccount;
|
||||||
} = getState().selectedAccount;
|
|
||||||
|
|
||||||
if (!account || account.networkType !== 'ethereum' || !network) return;
|
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 pendingNonce: number = reducerUtils.getPendingSequence(pending);
|
||||||
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
|
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
|
||||||
|
|
||||||
const txData = await dispatch(prepareEthereumTx({
|
const txData = await dispatch(
|
||||||
network: network.shortcut,
|
prepareEthereumTx({
|
||||||
token: isToken ? reducerUtils.findToken(getState().tokens, account.descriptor, currentState.currency, account.deviceState) : null,
|
network: network.shortcut,
|
||||||
from: account.descriptor,
|
token: isToken
|
||||||
to: currentState.address,
|
? reducerUtils.findToken(
|
||||||
amount: currentState.amount,
|
getState().tokens,
|
||||||
data: currentState.data,
|
account.descriptor,
|
||||||
gasLimit: currentState.gasLimit,
|
currentState.currency,
|
||||||
gasPrice: currentState.gasPrice,
|
account.deviceState
|
||||||
nonce,
|
)
|
||||||
}));
|
: 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;
|
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
@ -586,22 +694,28 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
total: currentState.total,
|
total: currentState.total,
|
||||||
|
|
||||||
sequence: nonce,
|
sequence: nonce,
|
||||||
tokens: isToken ? [{
|
tokens: isToken
|
||||||
name: currentState.currency,
|
? [
|
||||||
shortcut: currentState.currency,
|
{
|
||||||
value: currentState.amount,
|
name: currentState.currency,
|
||||||
}] : undefined,
|
shortcut: currentState.currency,
|
||||||
|
value: currentState.amount,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
|
|
||||||
blockHeight: 0,
|
blockHeight: 0,
|
||||||
blockHash: undefined,
|
blockHash: undefined,
|
||||||
timestamp: undefined,
|
timestamp: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(BlockchainActions.onNotification({
|
dispatch(
|
||||||
// $FlowIssue: missing coinInfo declaration
|
BlockchainActions.onNotification({
|
||||||
coin: {},
|
// $FlowIssue: missing coinInfo declaration
|
||||||
notification: blockchainNotification,
|
coin: {},
|
||||||
}));
|
notification: blockchainNotification,
|
||||||
|
})
|
||||||
|
);
|
||||||
// workaround end
|
// workaround end
|
||||||
|
|
||||||
// clear session storage
|
// clear session storage
|
||||||
@ -615,7 +729,11 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
payload: {
|
payload: {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Transaction 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,
|
cancelable: true,
|
||||||
actions: [],
|
actions: [],
|
||||||
},
|
},
|
||||||
@ -649,4 +767,4 @@ export default {
|
|||||||
onDataChange,
|
onDataChange,
|
||||||
onSend,
|
onSend,
|
||||||
onClear,
|
onClear,
|
||||||
};
|
};
|
||||||
|
@ -7,30 +7,33 @@ import { findDevice, getPendingAmount, findToken } from 'reducers/utils';
|
|||||||
import * as SEND from 'actions/constants/send';
|
import * as SEND from 'actions/constants/send';
|
||||||
import * as ethUtils from 'utils/ethUtils';
|
import * as ethUtils from 'utils/ethUtils';
|
||||||
|
|
||||||
import type {
|
import type { Dispatch, GetState, PayloadAction } from 'flowtype';
|
||||||
Dispatch,
|
|
||||||
GetState,
|
|
||||||
PayloadAction,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { State, FeeLevel } from 'reducers/SendFormEthereumReducer';
|
import type { State, FeeLevel } from 'reducers/SendFormEthereumReducer';
|
||||||
|
|
||||||
// general regular expressions
|
// general regular expressions
|
||||||
const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$');
|
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 UPPERCASE_RE = new RegExp('^(.*[A-Z].*)$');
|
||||||
const ABS_RE = new RegExp('^[0-9]+$');
|
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 => {
|
const dynamicRegexp = (decimals: number): RegExp => {
|
||||||
if (decimals > 0) {
|
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;
|
return ABS_RE;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from SendFormActions.observe
|
* Called from SendFormActions.observe
|
||||||
* Reaction for WEB3.GAS_PRICE_UPDATED action
|
* Reaction for WEB3.GAS_PRICE_UPDATED action
|
||||||
*/
|
*/
|
||||||
export const onGasPriceUpdated = (network: string, gasPrice: string): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
|
export const onGasPriceUpdated = (network: string, gasPrice: string): PayloadAction<void> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
// testing random data
|
// testing random data
|
||||||
// function getRandomInt(min, max) {
|
// function getRandomInt(min, max) {
|
||||||
// return Math.floor(Math.random() * (max - min + 1)) + min;
|
// 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
|
* Recalculate amount, total and fees
|
||||||
*/
|
*/
|
||||||
export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const validation = (): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
// clone deep nested object
|
// clone deep nested object
|
||||||
// to avoid overrides across state history
|
// to avoid overrides across state history
|
||||||
let state: State = JSON.parse(JSON.stringify(getState().sendFormEthereum));
|
let state: State = JSON.parse(JSON.stringify(getState().sendFormEthereum));
|
||||||
@ -99,12 +105,11 @@ export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getSt
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
tokens,
|
): State => {
|
||||||
pending,
|
const { account, tokens, pending } = getState().selectedAccount;
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!account) return $state;
|
if (!account) return $state;
|
||||||
|
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
@ -113,7 +118,12 @@ export const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
|
|||||||
if (state.setMax) {
|
if (state.setMax) {
|
||||||
const pendingAmount = getPendingAmount(pending, state.currency, isToken);
|
const pendingAmount = getPendingAmount(pending, state.currency, isToken);
|
||||||
if (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) {
|
if (token) {
|
||||||
state.amount = new BigNumber(token.balance).minus(pendingAmount).toFixed();
|
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 => {
|
export const addressValidation = ($state: State): PayloadAction<State> => (): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.address) return state;
|
if (!state.touched.address) return state;
|
||||||
@ -159,36 +169,52 @@ export const addressValidation = ($state: State): PayloadAction<State> => (): St
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Address label assignation
|
* Address label assignation
|
||||||
*/
|
*/
|
||||||
export const addressLabel = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const addressLabel = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.address || state.errors.address) return state;
|
if (!state.touched.address || state.errors.address) return state;
|
||||||
|
|
||||||
const {
|
const { account, network } = getState().selectedAccount;
|
||||||
account,
|
|
||||||
network,
|
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!account || !network) return state;
|
if (!account || !network) return state;
|
||||||
const { address } = 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) {
|
if (savedAccounts.length > 0) {
|
||||||
// check if found account belongs to this network
|
// check if found account belongs to this network
|
||||||
const currentNetworkAccount = savedAccounts.find(a => a.network === network.shortcut);
|
const currentNetworkAccount = savedAccounts.find(a => a.network === network.shortcut);
|
||||||
if (currentNetworkAccount) {
|
if (currentNetworkAccount) {
|
||||||
const device = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState);
|
const device = findDevice(
|
||||||
|
getState().devices,
|
||||||
|
currentNetworkAccount.deviceID,
|
||||||
|
currentNetworkAccount.deviceState
|
||||||
|
);
|
||||||
if (device) {
|
if (device) {
|
||||||
state.infos.address = `${device.instanceLabel} Account #${(currentNetworkAccount.index + 1)}`;
|
state.infos.address = `${
|
||||||
|
device.instanceLabel
|
||||||
|
} Account #${currentNetworkAccount.index + 1}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// corner-case: the same derivation path is used on different networks
|
// corner-case: the same derivation path is used on different networks
|
||||||
const otherNetworkAccount = savedAccounts[0];
|
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 { networks } = getState().localStorage.config;
|
||||||
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
|
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
|
||||||
if (device && otherNetwork) {
|
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
|
* Amount value validation
|
||||||
*/
|
*/
|
||||||
export const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const amountValidation = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.amount) return state;
|
if (!state.touched.amount) return state;
|
||||||
|
|
||||||
const {
|
const { account, tokens, pending } = getState().selectedAccount;
|
||||||
account,
|
|
||||||
tokens,
|
|
||||||
pending,
|
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!account) return state;
|
if (!account) return state;
|
||||||
|
|
||||||
const { amount } = state;
|
const { amount } = state;
|
||||||
@ -220,7 +245,12 @@ export const amountValidation = ($state: State): PayloadAction<State> => (dispat
|
|||||||
const pendingAmount: BigNumber = getPendingAmount(pending, state.currency, isToken);
|
const pendingAmount: BigNumber = getPendingAmount(pending, state.currency, isToken);
|
||||||
|
|
||||||
if (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;
|
if (!token) return state;
|
||||||
const decimalRegExp = dynamicRegexp(parseInt(token.decimals, 0));
|
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`;
|
state.errors.amount = `Maximum ${token.decimals} decimals allowed`;
|
||||||
} else if (new BigNumber(state.total).isGreaterThan(account.balance)) {
|
} else if (new BigNumber(state.total).isGreaterThan(account.balance)) {
|
||||||
state.errors.amount = `Not enough ${state.networkSymbol} to cover transaction fee`;
|
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';
|
state.errors.amount = 'Not enough funds';
|
||||||
} else if (new BigNumber(state.amount).isLessThanOrEqualTo('0')) {
|
} else if (new BigNumber(state.amount).isLessThanOrEqualTo('0')) {
|
||||||
state.errors.amount = 'Amount is too low';
|
state.errors.amount = 'Amount is too low';
|
||||||
}
|
}
|
||||||
} else if (!state.amount.match(ETH_18_RE)) {
|
} else if (!state.amount.match(ETH_18_RE)) {
|
||||||
state.errors.amount = 'Maximum 18 decimals allowed';
|
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';
|
state.errors.amount = 'Not enough funds';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,15 +281,16 @@ export const amountValidation = ($state: State): PayloadAction<State> => (dispat
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gas limit value validation
|
* Gas limit value validation
|
||||||
*/
|
*/
|
||||||
export const gasLimitValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const gasLimitValidation = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.gasLimit) return state;
|
if (!state.touched.gasLimit) return state;
|
||||||
|
|
||||||
const {
|
const { network } = getState().selectedAccount;
|
||||||
network,
|
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!network) return state;
|
if (!network) return state;
|
||||||
|
|
||||||
const { gasLimit } = state;
|
const { gasLimit } = state;
|
||||||
@ -263,7 +302,13 @@ export const gasLimitValidation = ($state: State): PayloadAction<State> => (disp
|
|||||||
const gl: BigNumber = new BigNumber(gasLimit);
|
const gl: BigNumber = new BigNumber(gasLimit);
|
||||||
if (gl.isLessThan(1)) {
|
if (gl.isLessThan(1)) {
|
||||||
state.errors.gasLimit = 'Gas limit is too low';
|
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';
|
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 => {
|
export const gasPriceValidation = ($state: State): PayloadAction<State> => (): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.gasPrice) return state;
|
if (!state.touched.gasPrice) return state;
|
||||||
@ -294,15 +339,16 @@ export const gasPriceValidation = ($state: State): PayloadAction<State> => (): S
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Nonce value validation
|
* Nonce value validation
|
||||||
*/
|
*/
|
||||||
export const nonceValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const nonceValidation = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.nonce) return state;
|
if (!state.touched.nonce) return state;
|
||||||
|
|
||||||
const {
|
const { account } = getState().selectedAccount;
|
||||||
account,
|
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!account || account.networkType !== 'ethereum') return state;
|
if (!account || account.networkType !== 'ethereum') return state;
|
||||||
|
|
||||||
const { nonce } = 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 => {
|
export const dataValidation = ($state: State): PayloadAction<State> => (): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.data || state.data.length === 0) return 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 => {
|
export const calculateFee = (gasPrice: string, gasLimit: string): string => {
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
return '0';
|
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 {
|
try {
|
||||||
// TODO - minus pendings
|
// TODO - minus pendings
|
||||||
const fee = calculateFee(gasPrice, gasLimit);
|
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 price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice;
|
||||||
const quarter: BigNumber = price.dividedBy(4);
|
const quarter: BigNumber = price.dividedBy(4);
|
||||||
const high: string = price.plus(quarter.times(2)).toFixed();
|
const high: string = price.plus(quarter.times(2)).toFixed();
|
||||||
const low: string = price.minus(quarter.times(2)).toFixed();
|
const low: string = price.minus(quarter.times(2)).toFixed();
|
||||||
|
|
||||||
const customLevel: FeeLevel = selected && selected.value === 'Custom' ? {
|
const customLevel: FeeLevel =
|
||||||
value: 'Custom',
|
selected && selected.value === 'Custom'
|
||||||
gasPrice: selected.gasPrice,
|
? {
|
||||||
// label: `${ calculateFee(gasPrice, gasLimit) } ${ symbol }`
|
value: 'Custom',
|
||||||
label: `${calculateFee(selected.gasPrice, gasLimit)} ${symbol}`,
|
gasPrice: selected.gasPrice,
|
||||||
} : {
|
// label: `${ calculateFee(gasPrice, gasLimit) } ${ symbol }`
|
||||||
value: 'Custom',
|
label: `${calculateFee(selected.gasPrice, gasLimit)} ${symbol}`,
|
||||||
gasPrice: low,
|
}
|
||||||
label: '',
|
: {
|
||||||
};
|
value: 'Custom',
|
||||||
|
gasPrice: low,
|
||||||
|
label: '',
|
||||||
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -416,4 +478,4 @@ export const getSelectedFeeLevel = (feeLevels: Array<FeeLevel>, selected: FeeLev
|
|||||||
selectedFeeLevel = feeLevels.find(f => f.value === 'Normal');
|
selectedFeeLevel = feeLevels.find(f => f.value === 'Normal');
|
||||||
}
|
}
|
||||||
return selectedFeeLevel || selected;
|
return selectedFeeLevel || selected;
|
||||||
};
|
};
|
||||||
|
@ -17,8 +17,13 @@ import type {
|
|||||||
BlockchainFeeLevel,
|
BlockchainFeeLevel,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const subscribe = (network: string): PromiseAction<void> => async (
|
||||||
const accounts: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.descriptor);
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
|
const accounts: Array<string> = getState()
|
||||||
|
.accounts.filter(a => a.network === network)
|
||||||
|
.map(a => a.descriptor);
|
||||||
await TrezorConnect.blockchainSubscribe({
|
await TrezorConnect.blockchainSubscribe({
|
||||||
accounts,
|
accounts,
|
||||||
coin: network,
|
coin: network,
|
||||||
@ -28,7 +33,10 @@ export const subscribe = (network: string): PromiseAction<void> => async (dispat
|
|||||||
// Get current known fee
|
// Get current known fee
|
||||||
// Use default values from appConfig.json if it wasn't downloaded from blockchain yet
|
// Use default values from appConfig.json if it wasn't downloaded from blockchain yet
|
||||||
// update them later, after onBlockMined event
|
// 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);
|
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||||
if (!blockchain || blockchain.feeLevels.length < 1) {
|
if (!blockchain || blockchain.feeLevels.length < 1) {
|
||||||
return network.fee.levels.map(level => ({
|
return network.fee.levels.map(level => ({
|
||||||
@ -39,7 +47,10 @@ export const getFeeLevels = (network: Network): PayloadAction<Array<BlockchainFe
|
|||||||
return blockchain.feeLevels;
|
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);
|
const blockchain = getState().blockchain.find(b => b.shortcut === network);
|
||||||
if (!blockchain) return; // flowtype fallback
|
if (!blockchain) return; // flowtype fallback
|
||||||
|
|
||||||
@ -69,9 +80,7 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (dis
|
|||||||
// level: 'transactions',
|
// level: 'transactions',
|
||||||
// coin: network,
|
// coin: network,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// if (!response.success) return;
|
// if (!response.success) return;
|
||||||
|
|
||||||
// response.payload.forEach((a, i) => {
|
// response.payload.forEach((a, i) => {
|
||||||
// if (a.transactions.length > 0) {
|
// if (a.transactions.length > 0) {
|
||||||
// console.warn('APDEJTED!', a, i);
|
// 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 { notification } = payload;
|
||||||
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
||||||
if (!account) return;
|
if (!account) return;
|
||||||
@ -103,7 +114,10 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
|
|||||||
network: account.network,
|
network: account.network,
|
||||||
|
|
||||||
amount: toDecimalAmount(notification.amount, network.decimals),
|
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),
|
fee: toDecimalAmount(notification.fee, network.decimals),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -126,13 +140,18 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
|
|||||||
});
|
});
|
||||||
if (!updatedAccount.success) return;
|
if (!updatedAccount.success) return;
|
||||||
|
|
||||||
dispatch(AccountsActions.update({
|
dispatch(
|
||||||
networkType: 'ripple',
|
AccountsActions.update({
|
||||||
...account,
|
networkType: 'ripple',
|
||||||
balance: toDecimalAmount(updatedAccount.payload.balance, network.decimals),
|
...account,
|
||||||
availableBalance: toDecimalAmount(updatedAccount.payload.availableBalance, network.decimals),
|
balance: toDecimalAmount(updatedAccount.payload.balance, network.decimals),
|
||||||
block: updatedAccount.payload.block,
|
availableBalance: toDecimalAmount(
|
||||||
sequence: updatedAccount.payload.sequence,
|
updatedAccount.payload.availableBalance,
|
||||||
reserve: '0',
|
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 * as DISCOVERY from 'actions/constants/discovery';
|
||||||
import { toDecimalAmount } from 'utils/formatUtils';
|
import { toDecimalAmount } from 'utils/formatUtils';
|
||||||
|
|
||||||
import type {
|
import type { PromiseAction, GetState, Dispatch, TrezorDevice, Network, Account } from 'flowtype';
|
||||||
PromiseAction,
|
|
||||||
GetState,
|
|
||||||
Dispatch,
|
|
||||||
TrezorDevice,
|
|
||||||
Network,
|
|
||||||
Account,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
import type { Discovery } from 'reducers/DiscoveryReducer';
|
||||||
|
|
||||||
export type DiscoveryStartAction = {
|
export type DiscoveryStartAction = {
|
||||||
@ -21,14 +14,20 @@ export type DiscoveryStartAction = {
|
|||||||
device: TrezorDevice,
|
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,
|
type: DISCOVERY.START,
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
network,
|
network,
|
||||||
device,
|
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 { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
||||||
if (!network) throw new Error('Discovery network not found');
|
if (!network) throw new Error('Discovery network not found');
|
||||||
@ -78,4 +77,4 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover
|
|||||||
sequence: account.sequence,
|
sequence: account.sequence,
|
||||||
reserve: toDecimalAmount(account.reserve, network.decimals),
|
reserve: toDecimalAmount(account.reserve, network.decimals),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -23,9 +23,12 @@ import * as BlockchainActions from './BlockchainActions';
|
|||||||
import * as ValidationActions from './SendFormValidationActions';
|
import * as ValidationActions from './SendFormValidationActions';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from WalletService
|
* Called from WalletService
|
||||||
*/
|
*/
|
||||||
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const observe = (prevState: ReducersState, action: Action): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const currentState = getState();
|
const currentState = getState();
|
||||||
|
|
||||||
// if action type is SEND.VALIDATION which is called as result of this process
|
// 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;
|
let shouldUpdate: boolean = false;
|
||||||
// check if "selectedAccount" reducer changed
|
// check if "selectedAccount" reducer changed
|
||||||
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
|
shouldUpdate = reducerUtils.observeChanges(
|
||||||
account: ['balance', 'sequence'],
|
prevState.selectedAccount,
|
||||||
});
|
currentState.selectedAccount,
|
||||||
|
{
|
||||||
|
account: ['balance', 'sequence'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// check if "sendForm" reducer changed
|
// check if "sendForm" reducer changed
|
||||||
if (!shouldUpdate) {
|
if (!shouldUpdate) {
|
||||||
shouldUpdate = reducerUtils.observeChanges(prevState.sendFormRipple, currentState.sendFormRipple);
|
shouldUpdate = reducerUtils.observeChanges(
|
||||||
|
prevState.sendFormRipple,
|
||||||
|
currentState.sendFormRipple
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
@ -70,15 +80,15 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from "observe" action
|
* Called from "observe" action
|
||||||
* Initialize "sendFormRipple" reducer data
|
* Initialize "sendFormRipple" reducer data
|
||||||
* Get data either from session storage or "selectedAccount" reducer
|
* Get data either from session storage or "selectedAccount" reducer
|
||||||
*/
|
*/
|
||||||
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const init = (): AsyncAction => async (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): Promise<void> => {
|
||||||
} = getState().selectedAccount;
|
const { account, network } = getState().selectedAccount;
|
||||||
|
|
||||||
if (!account || account.networkType !== 'ripple' || !network) return;
|
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 blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
||||||
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
|
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
initialState.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.INIT,
|
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 => ({
|
export const toggleAdvanced = (): Action => ({
|
||||||
type: SEND.TOGGLE_ADVANCED,
|
type: SEND.TOGGLE_ADVANCED,
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI from "clear" button
|
* Called from UI from "clear" button
|
||||||
*/
|
*/
|
||||||
export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onClear = (): AsyncAction => async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
const { advanced } = getState().sendFormRipple;
|
const { advanced } = getState().sendFormRipple;
|
||||||
|
|
||||||
@ -133,7 +149,10 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
|
|||||||
|
|
||||||
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
||||||
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
|
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
initialState.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CLEAR,
|
type: SEND.CLEAR,
|
||||||
@ -152,9 +171,12 @@ export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: G
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "address" field change
|
* Called from UI on "address" field change
|
||||||
*/
|
*/
|
||||||
export const onAddressChange = (address: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onAddressChange = (address: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state: State = getState().sendFormRipple;
|
const state: State = getState().sendFormRipple;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -169,9 +191,12 @@ export const onAddressChange = (address: string): ThunkAction => (dispatch: Disp
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "amount" field change
|
* Called from UI on "amount" field change
|
||||||
*/
|
*/
|
||||||
export const onAmountChange = (amount: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onAmountChange = (amount: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state = getState().sendFormRipple;
|
const state = getState().sendFormRipple;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
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 => {
|
export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const state = getState().sendFormRipple;
|
const state = getState().sendFormRipple;
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -204,9 +229,12 @@ export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "fee" selection change
|
* Called from UI on "fee" selection change
|
||||||
*/
|
*/
|
||||||
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state = getState().sendFormRipple;
|
const state = getState().sendFormRipple;
|
||||||
|
|
||||||
const isCustom = feeLevel.value === 'Custom';
|
const isCustom = feeLevel.value === 'Custom';
|
||||||
@ -225,19 +253,24 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI from "update recommended fees" button
|
* Called from UI from "update recommended fees" button
|
||||||
*/
|
*/
|
||||||
export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const updateFeeLevels = (): ThunkAction => (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): void => {
|
||||||
} = getState().selectedAccount;
|
const { account, network } = getState().selectedAccount;
|
||||||
if (!account || !network) return;
|
if (!account || !network) return;
|
||||||
|
|
||||||
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
||||||
const state: State = getState().sendFormRipple;
|
const state: State = getState().sendFormRipple;
|
||||||
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels, state.selectedFeeLevel));
|
const feeLevels = dispatch(
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
|
ValidationActions.getFeeLevels(blockchainFeeLevels, state.selectedFeeLevel)
|
||||||
|
);
|
||||||
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(
|
||||||
|
feeLevels,
|
||||||
|
state.selectedFeeLevel
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -252,16 +285,20 @@ export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "advanced / fee" field change
|
* Called from UI on "advanced / fee" field change
|
||||||
*/
|
*/
|
||||||
export const onFeeChange = (fee: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onFeeChange = (fee: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
const state: State = getState().sendFormRipple;
|
const state: State = getState().sendFormRipple;
|
||||||
|
|
||||||
// switch to custom fee level
|
// switch to custom fee level
|
||||||
let newSelectedFeeLevel = state.selectedFeeLevel;
|
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({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
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
|
* Called from UI on "advanced / destination tag" field change
|
||||||
*/
|
*/
|
||||||
export const onDestinationTagChange = (destinationTag: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const onDestinationTagChange = (destinationTag: string): ThunkAction => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): void => {
|
||||||
const state: State = getState().sendFormRipple;
|
const state: State = getState().sendFormRipple;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
@ -294,13 +334,13 @@ export const onDestinationTagChange = (destinationTag: string): ThunkAction => (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI from "send" button
|
* Called from UI from "send" button
|
||||||
*/
|
*/
|
||||||
export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onSend = (): AsyncAction => async (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): Promise<void> => {
|
||||||
} = getState().selectedAccount;
|
const { account, network } = getState().selectedAccount;
|
||||||
|
|
||||||
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
@ -401,4 +441,4 @@ export default {
|
|||||||
onDestinationTagChange,
|
onDestinationTagChange,
|
||||||
onSend,
|
onSend,
|
||||||
onClear,
|
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})$');
|
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
|
* Called from SendFormActions.observe
|
||||||
* Reaction for BLOCKCHAIN.FEE_UPDATED action
|
* Reaction for BLOCKCHAIN.FEE_UPDATED action
|
||||||
*/
|
*/
|
||||||
export const onFeeUpdated = (network: string, feeLevels: Array<BlockchainFeeLevel>): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
|
export const onFeeUpdated = (
|
||||||
|
network: string,
|
||||||
|
feeLevels: Array<BlockchainFeeLevel>
|
||||||
|
): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const state = getState().sendFormRipple;
|
const state = getState().sendFormRipple;
|
||||||
if (network === state.networkSymbol) return;
|
if (network === state.networkSymbol) return;
|
||||||
|
|
||||||
@ -58,9 +61,12 @@ export const onFeeUpdated = (network: string, feeLevels: Array<BlockchainFeeLeve
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Recalculate amount, total and fees
|
* Recalculate amount, total and fees
|
||||||
*/
|
*/
|
||||||
export const validation = (prevState: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const validation = (prevState: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
// clone deep nested object
|
// clone deep nested object
|
||||||
// to avoid overrides across state history
|
// to avoid overrides across state history
|
||||||
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
|
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
|
||||||
@ -81,12 +87,11 @@ export const validation = (prevState: State): PayloadAction<State> => (dispatch:
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
|
||||||
const {
|
dispatch: Dispatch,
|
||||||
account,
|
getState: GetState
|
||||||
network,
|
): State => {
|
||||||
pending,
|
const { account, network, pending } = getState().selectedAccount;
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!account || account.networkType !== 'ripple' || !network) return $state;
|
if (!account || account.networkType !== 'ripple' || !network) return $state;
|
||||||
|
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
@ -94,7 +99,9 @@ const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatc
|
|||||||
|
|
||||||
if (state.setMax) {
|
if (state.setMax) {
|
||||||
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
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);
|
state.amount = calculateMaxAmount(availableBalance, fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +109,10 @@ const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatc
|
|||||||
return state;
|
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;
|
const { network } = getState().selectedAccount;
|
||||||
if (!network) return $state; // flowtype fallback
|
if (!network) return $state; // flowtype fallback
|
||||||
|
|
||||||
@ -118,9 +128,12 @@ const updateCustomFeeLabel = ($state: State): PayloadAction<State> => (dispatch:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Address value validation
|
* Address value validation
|
||||||
*/
|
*/
|
||||||
const addressValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
const addressValidation = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.address) return state;
|
if (!state.touched.address) return state;
|
||||||
|
|
||||||
@ -140,10 +153,13 @@ const addressValidation = ($state: State): PayloadAction<State> => (dispatch: Di
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Address balance validation
|
* Address balance validation
|
||||||
* Fetch data from trezor-connect and set minimum required amount in reducer
|
* 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 addressBalanceValidation = ($state: State): PromiseAction<void> => async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
const { network } = getState().selectedAccount;
|
const { network } = getState().selectedAccount;
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
|
|
||||||
@ -177,36 +193,52 @@ const addressBalanceValidation = ($state: State): PromiseAction<void> => async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Address label assignation
|
* Address label assignation
|
||||||
*/
|
*/
|
||||||
const addressLabel = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
const addressLabel = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.address || state.errors.address) return state;
|
if (!state.touched.address || state.errors.address) return state;
|
||||||
|
|
||||||
const {
|
const { account, network } = getState().selectedAccount;
|
||||||
account,
|
|
||||||
network,
|
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!account || !network) return state;
|
if (!account || !network) return state;
|
||||||
const { address } = 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) {
|
if (savedAccounts.length > 0) {
|
||||||
// check if found account belongs to this network
|
// check if found account belongs to this network
|
||||||
const currentNetworkAccount = savedAccounts.find(a => a.network === network.shortcut);
|
const currentNetworkAccount = savedAccounts.find(a => a.network === network.shortcut);
|
||||||
if (currentNetworkAccount) {
|
if (currentNetworkAccount) {
|
||||||
const device = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState);
|
const device = findDevice(
|
||||||
|
getState().devices,
|
||||||
|
currentNetworkAccount.deviceID,
|
||||||
|
currentNetworkAccount.deviceState
|
||||||
|
);
|
||||||
if (device) {
|
if (device) {
|
||||||
state.infos.address = `${device.instanceLabel} Account #${(currentNetworkAccount.index + 1)}`;
|
state.infos.address = `${
|
||||||
|
device.instanceLabel
|
||||||
|
} Account #${currentNetworkAccount.index + 1}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// corner-case: the same derivation path is used on different networks
|
// corner-case: the same derivation path is used on different networks
|
||||||
const otherNetworkAccount = savedAccounts[0];
|
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 { networks } = getState().localStorage.config;
|
||||||
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
|
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
|
||||||
if (device && otherNetwork) {
|
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
|
* Amount value validation
|
||||||
*/
|
*/
|
||||||
const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
const amountValidation = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.amount) return state;
|
if (!state.touched.amount) return state;
|
||||||
|
|
||||||
const {
|
const { account, pending } = getState().selectedAccount;
|
||||||
account,
|
|
||||||
pending,
|
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!account || account.networkType !== 'ripple') return state;
|
if (!account || account.networkType !== 'ripple') return state;
|
||||||
|
|
||||||
const { amount } = state;
|
const { amount } = state;
|
||||||
@ -236,32 +268,44 @@ const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dis
|
|||||||
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
|
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
|
||||||
if (!state.amount.match(XRP_6_RE)) {
|
if (!state.amount.match(XRP_6_RE)) {
|
||||||
state.errors.amount = 'Maximum 6 decimals allowed';
|
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';
|
state.errors.amount = 'Not enough funds';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.errors.amount && new BigNumber(account.balance).minus(state.total).lt(account.reserve)) {
|
if (
|
||||||
state.errors.amount = `Not enough funds. Reserved amount for this account is ${account.reserve} ${state.networkSymbol}`;
|
!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)) {
|
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;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fee value validation
|
* Fee value validation
|
||||||
*/
|
*/
|
||||||
export const feeValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const feeValidation = ($state: State): PayloadAction<State> => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.fee) return state;
|
if (!state.touched.fee) return state;
|
||||||
|
|
||||||
const {
|
const { network } = getState().selectedAccount;
|
||||||
network,
|
|
||||||
} = getState().selectedAccount;
|
|
||||||
if (!network) return state;
|
if (!network) return state;
|
||||||
|
|
||||||
const { fee } = state;
|
const { fee } = state;
|
||||||
@ -280,8 +324,8 @@ export const feeValidation = ($state: State): PayloadAction<State> => (dispatch:
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
* Destination Tag value validation
|
* Destination Tag value validation
|
||||||
*/
|
*/
|
||||||
export const destinationTagValidation = ($state: State): PayloadAction<State> => (): State => {
|
export const destinationTagValidation = ($state: State): PayloadAction<State> => (): State => {
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
if (!state.touched.destinationTag) return state;
|
if (!state.touched.destinationTag) return state;
|
||||||
@ -293,10 +337,9 @@ export const destinationTagValidation = ($state: State): PayloadAction<State> =>
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UTILITIES
|
* UTILITIES
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const calculateTotal = (amount: string, fee: string): string => {
|
const calculateTotal = (amount: string, fee: string): string => {
|
||||||
try {
|
try {
|
||||||
@ -323,7 +366,10 @@ const calculateMaxAmount = (balance: BigNumber, fee: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Generate FeeLevel dataset for "fee" select
|
// 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;
|
const { network } = getState().selectedAccount;
|
||||||
if (!network) return []; // flowtype fallback
|
if (!network) return []; // flowtype fallback
|
||||||
|
|
||||||
@ -335,15 +381,18 @@ export const getFeeLevels = (feeLevels: Array<BlockchainFeeLevel>, selected?: Fe
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// add "Custom" level
|
// add "Custom" level
|
||||||
const customLevel = selected && selected.value === 'Custom' ? {
|
const customLevel =
|
||||||
value: 'Custom',
|
selected && selected.value === 'Custom'
|
||||||
fee: selected.fee,
|
? {
|
||||||
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
|
value: 'Custom',
|
||||||
} : {
|
fee: selected.fee,
|
||||||
value: 'Custom',
|
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
|
||||||
fee: '0',
|
}
|
||||||
label: '',
|
: {
|
||||||
};
|
value: 'Custom',
|
||||||
|
fee: '0',
|
||||||
|
label: '',
|
||||||
|
};
|
||||||
|
|
||||||
return levels.concat([customLevel]);
|
return levels.concat([customLevel]);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@ import styled, { css } from 'styled-components';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FADE_IN } from 'config/animations';
|
import { FADE_IN } from 'config/animations';
|
||||||
|
|
||||||
|
|
||||||
const StyledBackdrop = styled.div`
|
const StyledBackdrop = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -11,21 +10,17 @@ const StyledBackdrop = styled.div`
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: rgba(0,0,0,0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
${props => props.animated && css`
|
${props =>
|
||||||
animation: ${FADE_IN} 0.3s;
|
props.animated &&
|
||||||
`};
|
css`
|
||||||
|
animation: ${FADE_IN} 0.3s;
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Backdrop = ({
|
const Backdrop = ({ className, show, animated, onClick }) =>
|
||||||
className,
|
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null;
|
||||||
show,
|
|
||||||
animated,
|
|
||||||
onClick,
|
|
||||||
}) => (
|
|
||||||
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null
|
|
||||||
);
|
|
||||||
|
|
||||||
Backdrop.propTypes = {
|
Backdrop.propTypes = {
|
||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
|
@ -17,8 +17,8 @@ type Props = {
|
|||||||
isWhite?: boolean,
|
isWhite?: boolean,
|
||||||
isWebUsb?: boolean,
|
isWebUsb?: boolean,
|
||||||
isTransparent?: boolean,
|
isTransparent?: boolean,
|
||||||
dataTest?: string
|
dataTest?: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.button`
|
const Wrapper = styled.button`
|
||||||
padding: ${props => (props.icon ? '4px 24px 4px 15px' : '11px 24px')};
|
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};
|
box-shadow: 0 0px 6px 0 ${colors.INPUT_FOCUSED_SHADOW};
|
||||||
}
|
}
|
||||||
|
|
||||||
${props => props.isDisabled && css`
|
${props =>
|
||||||
pointer-events: none;
|
props.isDisabled &&
|
||||||
color: ${colors.TEXT_SECONDARY};
|
css`
|
||||||
background: ${colors.GRAY_LIGHT};
|
pointer-events: none;
|
||||||
`}
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
background: ${colors.GRAY_LIGHT};
|
||||||
|
`}
|
||||||
|
|
||||||
${props => props.isWhite && css`
|
${props =>
|
||||||
background: ${colors.WHITE};
|
props.isWhite &&
|
||||||
color: ${colors.TEXT_SECONDARY};
|
css`
|
||||||
border: 1px solid ${colors.DIVIDER};
|
background: ${colors.WHITE};
|
||||||
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
border: 1px solid ${colors.DIVIDER};
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${colors.TEXT_PRIMARY};
|
color: ${colors.TEXT_PRIMARY};
|
||||||
background: ${colors.DIVIDER};
|
background: ${colors.DIVIDER};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: ${colors.TEXT_PRIMARY};
|
color: ${colors.TEXT_PRIMARY};
|
||||||
background: ${colors.DIVIDER};
|
background: ${colors.DIVIDER};
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${props => props.isTransparent && css`
|
${props =>
|
||||||
background: transparent;
|
props.isTransparent &&
|
||||||
border: 0px;
|
css`
|
||||||
color: ${colors.TEXT_SECONDARY};
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
color: ${colors.TEXT_PRIMARY};
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
color: ${colors.TEXT_PRIMARY};
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
border: 0px;
|
||||||
`}
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
|
||||||
${props => props.isWebUsb && css`
|
&:focus {
|
||||||
position: relative;
|
color: ${colors.TEXT_PRIMARY};
|
||||||
padding: 12px 24px 12px 40px;
|
box-shadow: none;
|
||||||
background: transparent;
|
}
|
||||||
color: ${colors.GREEN_PRIMARY};
|
|
||||||
border: 1px solid ${colors.GREEN_PRIMARY};
|
|
||||||
transition: ${TRANSITION.HOVER};
|
|
||||||
|
|
||||||
&:before,
|
&:hover,
|
||||||
&:after {
|
&:active {
|
||||||
content: '';
|
color: ${colors.TEXT_PRIMARY};
|
||||||
position: absolute;
|
background: transparent;
|
||||||
background: ${colors.GREEN_PRIMARY};
|
}
|
||||||
top: 0;
|
`}
|
||||||
bottom: 0;
|
|
||||||
margin: auto;
|
${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};
|
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,
|
&:before,
|
||||||
&:after {
|
&:after {
|
||||||
background: ${colors.WHITE};
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background: ${colors.GREEN_PRIMARY};
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
transition: ${TRANSITION.HOVER};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
&:before {
|
||||||
position: absolute;
|
width: 12px;
|
||||||
top: 0;
|
height: 2px;
|
||||||
left: 0;
|
left: 18px;
|
||||||
z-index: 1;
|
}
|
||||||
}
|
|
||||||
`}
|
&: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 = ({
|
const Button = ({
|
||||||
@ -182,4 +190,4 @@ Button.propTypes = {
|
|||||||
dataTest: PropTypes.string,
|
dataTest: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Button;
|
export default Button;
|
||||||
|
@ -12,7 +12,7 @@ type Props = {
|
|||||||
onClick: (event: KeyboardEvent) => void,
|
onClick: (event: KeyboardEvent) => void,
|
||||||
isChecked: boolean,
|
isChecked: boolean,
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -26,8 +26,7 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Tick = styled.div`
|
const Tick = styled.div``;
|
||||||
`;
|
|
||||||
|
|
||||||
const IconWrapper = styled.div`
|
const IconWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -42,9 +41,11 @@ const IconWrapper = styled.div`
|
|||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
${props => !props.isChecked && css`
|
${props =>
|
||||||
border: 1px solid ${colors.GREEN_PRIMARY};
|
!props.isChecked &&
|
||||||
`}
|
css`
|
||||||
|
border: 1px solid ${colors.GREEN_PRIMARY};
|
||||||
|
`}
|
||||||
background: ${props => (props.isChecked ? colors.GREEN_PRIMARY : colors.WHITE)};
|
background: ${props => (props.isChecked ? colors.GREEN_PRIMARY : colors.WHITE)};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -70,17 +71,9 @@ class Checkbox extends React.PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { isChecked, children, onClick } = this.props;
|
||||||
isChecked,
|
|
||||||
children,
|
|
||||||
onClick,
|
|
||||||
} = this.props;
|
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper onClick={onClick} onKeyUp={event => this.handleKeyboard(event)} tabIndex={0}>
|
||||||
onClick={onClick}
|
|
||||||
onKeyUp={event => this.handleKeyboard(event)}
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<IconWrapper isChecked={isChecked}>
|
<IconWrapper isChecked={isChecked}>
|
||||||
{isChecked && (
|
{isChecked && (
|
||||||
<Tick>
|
<Tick>
|
||||||
@ -91,8 +84,7 @@ class Checkbox extends React.PureComponent<Props> {
|
|||||||
icon={icons.SUCCESS}
|
icon={icons.SUCCESS}
|
||||||
/>
|
/>
|
||||||
</Tick>
|
</Tick>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<Label isChecked={isChecked}>{children}</Label>
|
<Label isChecked={isChecked}>{children}</Label>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
@ -2,12 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import { getStatusColor, getStatusName, getStatus, getVersion } from 'utils/device';
|
||||||
getStatusColor,
|
|
||||||
getStatusName,
|
|
||||||
getStatus,
|
|
||||||
getVersion,
|
|
||||||
} from 'utils/device';
|
|
||||||
import TrezorImage from 'components/images/TrezorImage';
|
import TrezorImage from 'components/images/TrezorImage';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
||||||
@ -27,19 +22,26 @@ const Wrapper = styled.div`
|
|||||||
border-radius: 4px 0 0 0;
|
border-radius: 4px 0 0 0;
|
||||||
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
|
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
|
||||||
|
|
||||||
${props => (props.isOpen || !props.isSelected) && css`
|
${props =>
|
||||||
box-shadow: none;
|
(props.isOpen || !props.isSelected) &&
|
||||||
`}
|
css`
|
||||||
|
box-shadow: none;
|
||||||
|
`}
|
||||||
|
|
||||||
${props => props.disabled && css`
|
${props =>
|
||||||
cursor: default;
|
props.disabled &&
|
||||||
`}
|
css`
|
||||||
|
cursor: default;
|
||||||
|
`}
|
||||||
|
|
||||||
${props => props.isHoverable && !props.disabled && css`
|
${props =>
|
||||||
&:hover {
|
props.isHoverable &&
|
||||||
background: ${colors.GRAY_LIGHT};
|
!props.disabled &&
|
||||||
}
|
css`
|
||||||
`}
|
&:hover {
|
||||||
|
background: ${colors.GRAY_LIGHT};
|
||||||
|
}
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LabelWrapper = styled.div`
|
const LabelWrapper = styled.div`
|
||||||
@ -88,7 +90,6 @@ const Dot = styled.div`
|
|||||||
height: 10px;
|
height: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const DeviceHeader = ({
|
const DeviceHeader = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
icon,
|
icon,
|
||||||
@ -120,9 +121,7 @@ const DeviceHeader = ({
|
|||||||
<Name>{device.instanceLabel}</Name>
|
<Name>{device.instanceLabel}</Name>
|
||||||
<Status title={getStatusName(status)}>{getStatusName(status)}</Status>
|
<Status title={getStatusName(status)}>{getStatusName(status)}</Status>
|
||||||
</LabelWrapper>
|
</LabelWrapper>
|
||||||
<IconWrapper>
|
<IconWrapper>{icon && !disabled && isAccessible && icon}</IconWrapper>
|
||||||
{icon && !disabled && isAccessible && icon}
|
|
||||||
</IconWrapper>
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,6 @@ import { bindActionCreators } from 'redux';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { FONT_SIZE } from 'config/variables';
|
import { FONT_SIZE } from 'config/variables';
|
||||||
import * as LogActions from 'actions/LogActions';
|
import * as LogActions from 'actions/LogActions';
|
||||||
@ -19,7 +18,7 @@ type Props = {
|
|||||||
opened: boolean,
|
opened: boolean,
|
||||||
isLanding: boolean,
|
isLanding: boolean,
|
||||||
toggle: () => any,
|
toggle: () => any,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -62,17 +61,27 @@ const Footer = ({ opened, toggle, isLanding }: Props) => (
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Left>
|
<Left>
|
||||||
<Copy>© {getYear(new Date())}</Copy>
|
<Copy>© {getYear(new Date())}</Copy>
|
||||||
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink>
|
<StyledLink href="http://satoshilabs.com" isGreen>
|
||||||
|
SatoshiLabs
|
||||||
|
</StyledLink>
|
||||||
<StyledLink href="https://trezor.io/static/pdf/tos.pdf" isGreen>
|
<StyledLink href="https://trezor.io/static/pdf/tos.pdf" isGreen>
|
||||||
<FormattedMessage {...l10nMessages.TR_TERMS} />
|
<FormattedMessage {...l10nMessages.TR_TERMS} />
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink>
|
<StyledLink onClick={toggle} isGreen>
|
||||||
|
{opened ? 'Hide Log' : 'Show Log'}
|
||||||
|
</StyledLink>
|
||||||
</Left>
|
</Left>
|
||||||
{!isLanding && (
|
{!isLanding && (
|
||||||
<Right>
|
<Right>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
{...l10nMessages.TR_EXCHANGE_RATES_BY}
|
{...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>
|
</Right>
|
||||||
)}
|
)}
|
||||||
@ -93,4 +102,7 @@ const mapDispatchToProps = dispatch => ({
|
|||||||
toggle: bindActionCreators(LogActions.toggle, 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 = {
|
type StateProps = {
|
||||||
language: string,
|
language: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
type DispatchProps = {
|
type DispatchProps = {
|
||||||
fetchLocale: typeof WalletActions.fetchLocale,
|
fetchLocale: typeof WalletActions.fetchLocale,
|
||||||
};
|
};
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Props = StateProps & DispatchProps;
|
export type Props = StateProps & DispatchProps;
|
||||||
|
|
||||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
|
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (
|
||||||
|
state: State
|
||||||
|
): StateProps => ({
|
||||||
language: state.wallet.language,
|
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),
|
fetchLocale: bindActionCreators(WalletActions.fetchLocale, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withRouter(
|
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);
|
const lang = LANGUAGE.find(l => l.code === langCode);
|
||||||
return { value: lang.code, label: lang.name };
|
return { value: lang.code, label: lang.name };
|
||||||
};
|
};
|
||||||
@ -99,9 +98,7 @@ const LanguagePicker = ({ language, fetchLocale }: Props) => (
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
onChange={option => fetchLocale(option.value)}
|
onChange={option => fetchLocale(option.value)}
|
||||||
value={buildOption(language)}
|
value={buildOption(language)}
|
||||||
options={
|
options={LANGUAGE.map(lang => buildOption(lang.code))}
|
||||||
LANGUAGE.map(lang => buildOption(lang.code))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</SelectWrapper>
|
</SelectWrapper>
|
||||||
);
|
);
|
||||||
|
@ -53,16 +53,14 @@ const MenuToggler = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: 10px 0px;
|
padding: 10px 0px;
|
||||||
transition: all .1s ease-in;
|
transition: all 0.1s ease-in;
|
||||||
|
|
||||||
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TogglerText = styled.div`
|
const TogglerText = styled.div``;
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TREZOR = styled.div``;
|
const TREZOR = styled.div``;
|
||||||
const T = styled.div``;
|
const T = styled.div``;
|
||||||
@ -75,11 +73,11 @@ const Logo = styled.div`
|
|||||||
${T} {
|
${T} {
|
||||||
display: none;
|
display: none;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
${TREZOR} {
|
${TREZOR} {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: ${colors.WHITE};
|
fill: ${colors.WHITE};
|
||||||
@ -95,11 +93,11 @@ const Logo = styled.div`
|
|||||||
/* hides full width trezor logo, shows only trezor icon */
|
/* hides full width trezor logo, shows only trezor icon */
|
||||||
${TREZOR} {
|
${TREZOR} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
${T} {
|
${T} {
|
||||||
display: inherit;
|
display: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -131,7 +129,7 @@ const Projects = styled.div`
|
|||||||
const A = styled.a`
|
const A = styled.a`
|
||||||
color: ${colors.WHITE};
|
color: ${colors.WHITE};
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
transition: all .1s ease-in;
|
transition: all 0.1s ease-in;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&:visited {
|
&:visited {
|
||||||
@ -153,33 +151,24 @@ type Props = {
|
|||||||
sidebarEnabled?: boolean,
|
sidebarEnabled?: boolean,
|
||||||
sidebarOpened?: ?boolean,
|
sidebarOpened?: ?boolean,
|
||||||
toggleSidebar?: toggleSidebarType,
|
toggleSidebar?: toggleSidebarType,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
||||||
<Wrapper data-test="Main__page__navigation">
|
<Wrapper data-test="Main__page__navigation">
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<Left>
|
<Left>
|
||||||
{ sidebarEnabled && (
|
{sidebarEnabled && (
|
||||||
<MenuToggler onClick={toggleSidebar}>
|
<MenuToggler onClick={toggleSidebar}>
|
||||||
{sidebarOpened ? (
|
{sidebarOpened ? (
|
||||||
<>
|
<>
|
||||||
<Icon
|
<Icon size={24} color={colors.WHITE} icon={icons.CLOSE} />
|
||||||
size={24}
|
|
||||||
color={colors.WHITE}
|
|
||||||
icon={icons.CLOSE}
|
|
||||||
/>
|
|
||||||
<TogglerText>
|
<TogglerText>
|
||||||
<FormattedMessage {...l10nMessages.TR_MENU_CLOSE} />
|
<FormattedMessage {...l10nMessages.TR_MENU_CLOSE} />
|
||||||
</TogglerText>
|
</TogglerText>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Icon
|
<Icon color={colors.WHITE} size={24} icon={icons.MENU} />
|
||||||
color={colors.WHITE}
|
|
||||||
size={24}
|
|
||||||
icon={icons.MENU}
|
|
||||||
/>
|
|
||||||
<TogglerText>
|
<TogglerText>
|
||||||
<FormattedMessage {...l10nMessages.TR_MENU} />
|
<FormattedMessage {...l10nMessages.TR_MENU} />
|
||||||
</TogglerText>
|
</TogglerText>
|
||||||
@ -191,7 +180,15 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
|||||||
<Logo>
|
<Logo>
|
||||||
<NavLink to="/">
|
<NavLink to="/">
|
||||||
<TREZOR>
|
<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" />
|
<polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1" />
|
||||||
<path d="M158.8,26.9c2.1-0.8,4.3-2.9,4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1,7.5h6.7L158.8,26.9z M154.7,22.5 h-4V18h4c1.5,0,2.5,0.9,2.5,2.2C157.2,21.6,156.2,22.5,154.7,22.5z" />
|
<path d="M158.8,26.9c2.1-0.8,4.3-2.9,4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1,7.5h6.7L158.8,26.9z M154.7,22.5 h-4V18h4c1.5,0,2.5,0.9,2.5,2.2C157.2,21.6,156.2,22.5,154.7,22.5z" />
|
||||||
<path d="M130.8,12.5c-6.8,0-11.6,4.9-11.6,11.5s4.9,11.5,11.6,11.5s11.7-4.9,11.7-11.5S137.6,12.5,130.8,12.5z M130.8,30.3 c-3.4,0-5.7-2.6-5.7-6.3c0-3.8,2.3-6.3,5.7-6.3c3.4,0,5.8,2.6,5.8,6.3C136.6,27.7,134.2,30.3,130.8,30.3z" />
|
<path d="M130.8,12.5c-6.8,0-11.6,4.9-11.6,11.5s4.9,11.5,11.6,11.5s11.7-4.9,11.7-11.5S137.6,12.5,130.8,12.5z M130.8,30.3 c-3.4,0-5.7-2.6-5.7-6.3c0-3.8,2.3-6.3,5.7-6.3c3.4,0,5.8,2.6,5.8,6.3C136.6,27.7,134.2,30.3,130.8,30.3z" />
|
||||||
@ -202,7 +199,15 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
|||||||
</svg>
|
</svg>
|
||||||
</TREZOR>
|
</TREZOR>
|
||||||
<T>
|
<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" />
|
<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>
|
</svg>
|
||||||
</T>
|
</T>
|
||||||
@ -210,10 +215,18 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
|||||||
</Logo>
|
</Logo>
|
||||||
<MenuLinks>
|
<MenuLinks>
|
||||||
<Projects>
|
<Projects>
|
||||||
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_TREZOR} /></A>
|
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">
|
||||||
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_WIKI} /></A>
|
<FormattedMessage {...l10nMessages.TR_TREZOR} />
|
||||||
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_BLOG} /></A>
|
</A>
|
||||||
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener"><FormattedMessage {...l10nMessages.TR_SUPPORT} /></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>
|
</Projects>
|
||||||
<LanguagePicker />
|
<LanguagePicker />
|
||||||
</MenuLinks>
|
</MenuLinks>
|
||||||
@ -221,4 +234,4 @@ const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
|||||||
</Wrapper>
|
</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};
|
font-size: ${FONT_SIZE.H2};
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
|
||||||
${props => props.claim
|
${props =>
|
||||||
&& css`
|
props.claim &&
|
||||||
|
css`
|
||||||
font-size: ${FONT_SIZE.HUGE};
|
font-size: ${FONT_SIZE.HUGE};
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
`};
|
`};
|
||||||
@ -41,6 +42,4 @@ const H4 = styled.h4`
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export {
|
export { H1, H2, H3, H4 };
|
||||||
H1, H2, H3, H4,
|
|
||||||
};
|
|
||||||
|
@ -17,7 +17,7 @@ type Props = {
|
|||||||
onMouseLeave?: () => void,
|
onMouseLeave?: () => void,
|
||||||
onFocus?: () => void,
|
onFocus?: () => void,
|
||||||
onClick?: () => void,
|
onClick?: () => void,
|
||||||
}
|
};
|
||||||
|
|
||||||
const chooseIconAnimationType = (canAnimate, isActive) => {
|
const chooseIconAnimationType = (canAnimate, isActive) => {
|
||||||
if (canAnimate) {
|
if (canAnimate) {
|
||||||
@ -49,11 +49,12 @@ const rotate180down = keyframes`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const SvgWrapper = styled.svg`
|
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 {
|
:hover {
|
||||||
path {
|
path {
|
||||||
fill: ${props => props.hoverColor}
|
fill: ${props => props.hoverColor};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -93,12 +94,7 @@ const Icon = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{icon.map(path => (
|
{icon.map(path => (
|
||||||
<Path
|
<Path key={path} isActive={isActive} color={color} d={path} />
|
||||||
key={path}
|
|
||||||
isActive={isActive}
|
|
||||||
color={color}
|
|
||||||
d={path}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</SvgWrapper>
|
</SvgWrapper>
|
||||||
);
|
);
|
||||||
@ -117,4 +113,4 @@ Icon.propTypes = {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Icon;
|
export default Icon;
|
||||||
|
@ -11,25 +11,33 @@ const A = styled.a`
|
|||||||
transition: ${TRANSITION.HOVER};
|
transition: ${TRANSITION.HOVER};
|
||||||
font-size: ${FONT_SIZE.SMALL};
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
|
|
||||||
${props => props.isGreen && css`
|
${props =>
|
||||||
text-decoration: underline;
|
props.isGreen &&
|
||||||
text-decoration-color: ${colors.GREEN_PRIMARY};
|
css`
|
||||||
`}
|
text-decoration: underline;
|
||||||
${props => props.isGray && css`
|
text-decoration-color: ${colors.GREEN_PRIMARY};
|
||||||
text-decoration: underline;
|
`}
|
||||||
text-decoration-color: ${colors.TEXT_SECONDARY};
|
${props =>
|
||||||
`}
|
props.isGray &&
|
||||||
|
css`
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-color: ${colors.TEXT_SECONDARY};
|
||||||
|
`}
|
||||||
|
|
||||||
&,
|
&,
|
||||||
&:visited,
|
&:visited,
|
||||||
&:active,
|
&:active,
|
||||||
&:hover {
|
&:hover {
|
||||||
${props => props.isGreen && css`
|
${props =>
|
||||||
color: ${colors.GREEN_PRIMARY};
|
props.isGreen &&
|
||||||
`}
|
css`
|
||||||
${props => props.isGray && css`
|
color: ${colors.GREEN_PRIMARY};
|
||||||
color: ${colors.TEXT_SECONDARY};
|
`}
|
||||||
`}
|
${props =>
|
||||||
|
props.isGray &&
|
||||||
|
css`
|
||||||
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
`}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -38,13 +46,17 @@ const A = styled.a`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledNavLink = styled(NavLink)`
|
const StyledNavLink = styled(NavLink)`
|
||||||
${props => props.isGreen && css`
|
${props =>
|
||||||
color: ${colors.GREEN_PRIMARY};
|
props.isGreen &&
|
||||||
`}
|
css`
|
||||||
|
color: ${colors.GREEN_PRIMARY};
|
||||||
|
`}
|
||||||
|
|
||||||
${props => props.isGray && css`
|
${props =>
|
||||||
color: ${colors.TEXT_SECONDARY};
|
props.isGray &&
|
||||||
`}
|
css`
|
||||||
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class Link extends PureComponent {
|
class Link extends PureComponent {
|
||||||
@ -52,8 +64,7 @@ class Link extends PureComponent {
|
|||||||
const shouldRenderRouterLink = this.props.to;
|
const shouldRenderRouterLink = this.props.to;
|
||||||
let LinkComponent;
|
let LinkComponent;
|
||||||
if (shouldRenderRouterLink) {
|
if (shouldRenderRouterLink) {
|
||||||
LinkComponent = (
|
LinkComponent = <StyledNavLink {...this.props}>{this.props.children}</StyledNavLink>;
|
||||||
<StyledNavLink {...this.props}>{this.props.children}</StyledNavLink>);
|
|
||||||
} else {
|
} else {
|
||||||
LinkComponent = (
|
LinkComponent = (
|
||||||
<A
|
<A
|
||||||
@ -61,7 +72,8 @@ class Link extends PureComponent {
|
|||||||
target={this.props.target || '_blank'}
|
target={this.props.target || '_blank'}
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
{...this.props}
|
{...this.props}
|
||||||
>{this.props.children}
|
>
|
||||||
|
{this.props.children}
|
||||||
</A>
|
</A>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -86,4 +98,4 @@ Link.propTypes = {
|
|||||||
isGray: PropTypes.bool,
|
isGray: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Link;
|
export default Link;
|
||||||
|
@ -24,17 +24,22 @@ const SvgWrapper = styled.svg`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const CircleWrapper = styled.circle`
|
const CircleWrapper = styled.circle`
|
||||||
${props => props.isRoute && css`
|
${props =>
|
||||||
stroke: ${props.transparentRoute ? 'transparent' : colors.GRAY_LIGHT};
|
props.isRoute &&
|
||||||
`}
|
css`
|
||||||
|
stroke: ${props.transparentRoute ? 'transparent' : colors.GRAY_LIGHT};
|
||||||
|
`}
|
||||||
|
|
||||||
${props => props.isPath && css`
|
${props =>
|
||||||
stroke-width: ${props.transparentRoute ? '2px' : '1px'};
|
props.isPath &&
|
||||||
stroke-dasharray: 1, 200;
|
css`
|
||||||
stroke-dashoffset: 0;
|
stroke-width: ${props.transparentRoute ? '2px' : '1px'};
|
||||||
animation: ${DASH} 1.5s ease-in-out infinite, ${props.animationColor || GREEN_COLOR} 6s ease-in-out infinite;
|
stroke-dasharray: 1, 200;
|
||||||
stroke-linecap: round;
|
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)`
|
const StyledParagraph = styled(Paragraph)`
|
||||||
@ -43,10 +48,18 @@ const StyledParagraph = styled(Paragraph)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Loader = ({
|
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}>
|
<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">
|
<SvgWrapper viewBox="25 25 50 50">
|
||||||
<CircleWrapper
|
<CircleWrapper
|
||||||
animationColor={animationColor}
|
animationColor={animationColor}
|
||||||
|
@ -10,7 +10,6 @@ import Icon from 'components/Icon';
|
|||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
|
||||||
import * as LogActions from 'actions/LogActions';
|
import * as LogActions from 'actions/LogActions';
|
||||||
import icons from 'config/icons';
|
import icons from 'config/icons';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
@ -18,8 +17,8 @@ import l10nMessages from './index.messages';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
log: $ElementType<State, 'log'>,
|
log: $ElementType<State, 'log'>,
|
||||||
toggle: typeof LogActions.toggle
|
toggle: typeof LogActions.toggle,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -86,5 +85,5 @@ export default connect(
|
|||||||
}),
|
}),
|
||||||
(dispatch: Dispatch) => ({
|
(dispatch: Dispatch) => ({
|
||||||
toggle: bindActionCreators(LogActions.toggle, dispatch),
|
toggle: bindActionCreators(LogActions.toggle, dispatch),
|
||||||
}),
|
})
|
||||||
)(Log);
|
)(Log);
|
||||||
|
@ -5,7 +5,8 @@ import type { Messages } from 'flowtype/npm/react-intl';
|
|||||||
const definedMessages: Messages = defineMessages({
|
const definedMessages: Messages = defineMessages({
|
||||||
TR_ATTENTION_COLON_THE_LOG_CONTAINS: {
|
TR_ATTENTION_COLON_THE_LOG_CONTAINS: {
|
||||||
id: '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: {
|
TR_LOG: {
|
||||||
id: '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 { WHITE_COLOR } from 'config/animations';
|
||||||
import { getPrimaryColor } from 'utils/notification';
|
import { getPrimaryColor } from 'utils/notification';
|
||||||
import Loader from 'components/Loader';
|
import Loader from 'components/Loader';
|
||||||
import {
|
import { TRANSITION, FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
|
||||||
TRANSITION, FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE,
|
|
||||||
} from 'config/variables';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: string;
|
type: string,
|
||||||
icon?: {
|
icon?: {
|
||||||
type: Array<string>;
|
type: Array<string>,
|
||||||
color: string;
|
color: string,
|
||||||
size: number;
|
size: number,
|
||||||
};
|
},
|
||||||
onClick: () => void;
|
onClick: () => void,
|
||||||
isLoading?: boolean;
|
isLoading?: boolean,
|
||||||
children: React.Node;
|
children: React.Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
const LoaderContent = styled.div`
|
const LoaderContent = styled.div`
|
||||||
@ -50,8 +48,8 @@ const Wrapper = styled.button`
|
|||||||
border: 1px solid ${props => getPrimaryColor(props.type)};
|
border: 1px solid ${props => getPrimaryColor(props.type)};
|
||||||
transition: ${TRANSITION.HOVER};
|
transition: ${TRANSITION.HOVER};
|
||||||
|
|
||||||
@media screen and (max-width: ${SCREEN_SIZE.SM}){
|
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -64,14 +62,8 @@ const IconWrapper = styled.span`
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const NotificationButton = ({
|
const NotificationButton = ({ type, icon, onClick, children, isLoading }: Props) => (
|
||||||
type, icon, onClick, children, isLoading,
|
<Wrapper icon={icon} onClick={onClick} type={type}>
|
||||||
}: Props) => (
|
|
||||||
<Wrapper
|
|
||||||
icon={icon}
|
|
||||||
onClick={onClick}
|
|
||||||
type={type}
|
|
||||||
>
|
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<LoaderContent type={type}>
|
<LoaderContent type={type}>
|
||||||
<Loader transparentRoute animationColor={WHITE_COLOR} size={30} />
|
<Loader transparentRoute animationColor={WHITE_COLOR} size={30} />
|
||||||
@ -79,11 +71,7 @@ const NotificationButton = ({
|
|||||||
)}
|
)}
|
||||||
{icon && (
|
{icon && (
|
||||||
<IconWrapper>
|
<IconWrapper>
|
||||||
<Icon
|
<Icon icon={icon.type} color={icon.color} size={icon.size} />
|
||||||
icon={icon.type}
|
|
||||||
color={icon.color}
|
|
||||||
size={icon.size}
|
|
||||||
/>
|
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
@ -102,4 +90,4 @@ NotificationButton.propTypes = {
|
|||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotificationButton;
|
export default NotificationButton;
|
||||||
|
@ -14,14 +14,14 @@ import NotificationButton from './components/NotificationButton';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: string,
|
type: string,
|
||||||
cancelable?: boolean;
|
cancelable?: boolean,
|
||||||
title: ?React.Node;
|
title: ?React.Node,
|
||||||
className?: string;
|
className?: string,
|
||||||
message?: ?React.Node;
|
message?: ?React.Node,
|
||||||
actions?: Array<CallbackAction>;
|
actions?: Array<CallbackAction>,
|
||||||
isActionInProgress?: boolean;
|
isActionInProgress?: boolean,
|
||||||
close?: typeof NotificationActions.close,
|
close?: typeof NotificationActions.close,
|
||||||
loading?: boolean
|
loading?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
@ -98,7 +98,7 @@ const Notification = (props: Props): React$Element<string> => {
|
|||||||
return (
|
return (
|
||||||
<Wrapper className={props.className} type={props.type}>
|
<Wrapper className={props.className} type={props.type}>
|
||||||
<Content>
|
<Content>
|
||||||
{props.loading && <Loader size={50} /> }
|
{props.loading && <Loader size={50} />}
|
||||||
<Body>
|
<Body>
|
||||||
<IconWrapper>
|
<IconWrapper>
|
||||||
<StyledIcon
|
<StyledIcon
|
||||||
@ -107,8 +107,8 @@ const Notification = (props: Props): React$Element<string> => {
|
|||||||
/>
|
/>
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<Texts>
|
<Texts>
|
||||||
<Title>{ props.title }</Title>
|
<Title>{props.title}</Title>
|
||||||
{ props.message ? <Message>{props.message}</Message> : '' }
|
{props.message ? <Message>{props.message}</Message> : ''}
|
||||||
</Texts>
|
</Texts>
|
||||||
</Body>
|
</Body>
|
||||||
<AdditionalContent>
|
<AdditionalContent>
|
||||||
@ -119,8 +119,12 @@ const Notification = (props: Props): React$Element<string> => {
|
|||||||
key={action.label}
|
key={action.label}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
isLoading={props.isActionInProgress}
|
isLoading={props.isActionInProgress}
|
||||||
onClick={() => { close(); action.callback(); }}
|
onClick={() => {
|
||||||
>{action.label}
|
close();
|
||||||
|
action.callback();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
</NotificationButton>
|
</NotificationButton>
|
||||||
))}
|
))}
|
||||||
</ActionContent>
|
</ActionContent>
|
||||||
@ -128,11 +132,7 @@ const Notification = (props: Props): React$Element<string> => {
|
|||||||
</AdditionalContent>
|
</AdditionalContent>
|
||||||
{props.cancelable && (
|
{props.cancelable && (
|
||||||
<CloseClick onClick={() => close()}>
|
<CloseClick onClick={() => close()}>
|
||||||
<Icon
|
<Icon color={getPrimaryColor(props.type)} icon={icons.CLOSE} size={20} />
|
||||||
color={getPrimaryColor(props.type)}
|
|
||||||
icon={icons.CLOSE}
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
</CloseClick>
|
</CloseClick>
|
||||||
)}
|
)}
|
||||||
</Content>
|
</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;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
${props => props.isSmaller && css`
|
${props =>
|
||||||
font-size: ${FONT_SIZE.SMALL};
|
props.isSmaller &&
|
||||||
`}
|
css`
|
||||||
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const P = ({ children, className, isSmaller = false }) => (
|
const P = ({ children, className, isSmaller = false }) => (
|
||||||
<Wrapper
|
<Wrapper className={className} isSmaller={isSmaller}>
|
||||||
className={className}
|
{children}
|
||||||
isSmaller={isSmaller}
|
|
||||||
>{children}
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ const styles = isSearchable => ({
|
|||||||
}),
|
}),
|
||||||
dropdownIndicator: (base, { isDisabled }) => ({
|
dropdownIndicator: (base, { isDisabled }) => ({
|
||||||
...base,
|
...base,
|
||||||
display: (isSearchable || isDisabled) ? 'none' : 'block',
|
display: isSearchable || isDisabled ? 'none' : 'block',
|
||||||
color: colors.TEXT_SECONDARY,
|
color: colors.TEXT_SECONDARY,
|
||||||
path: '',
|
path: '',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
@ -61,7 +61,6 @@ const styles = isSearchable => ({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
isAsync: PropTypes.bool,
|
isAsync: PropTypes.bool,
|
||||||
isSearchable: PropTypes.bool,
|
isSearchable: PropTypes.bool,
|
||||||
@ -71,7 +70,4 @@ const AsyncSelect = props => <ReactAsyncSelect styles={styles(props.isSearchable
|
|||||||
Select.propTypes = propTypes;
|
Select.propTypes = propTypes;
|
||||||
AsyncSelect.propTypes = propTypes;
|
AsyncSelect.propTypes = propTypes;
|
||||||
|
|
||||||
export {
|
export { Select, AsyncSelect };
|
||||||
Select,
|
|
||||||
AsyncSelect,
|
|
||||||
};
|
|
||||||
|
@ -3,12 +3,7 @@ import Textarea from 'react-textarea-autosize';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import {
|
import { FONT_SIZE, FONT_WEIGHT, LINE_HEIGHT, FONT_FAMILY } from 'config/variables';
|
||||||
FONT_SIZE,
|
|
||||||
FONT_WEIGHT,
|
|
||||||
LINE_HEIGHT,
|
|
||||||
FONT_FAMILY,
|
|
||||||
} from 'config/variables';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -34,11 +29,11 @@ const StyledTextarea = styled(Textarea)`
|
|||||||
background: ${colors.WHITE};
|
background: ${colors.WHITE};
|
||||||
font-weight: ${FONT_WEIGHT.MEDIUM};
|
font-weight: ${FONT_WEIGHT.MEDIUM};
|
||||||
font-size: ${FONT_SIZE.BASE};
|
font-size: ${FONT_SIZE.BASE};
|
||||||
white-space: pre-wrap; /* css-3 */
|
white-space: pre-wrap; /* css-3 */
|
||||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||||
white-space: -pre-wrap; /* Opera 4-6 */
|
white-space: -pre-wrap; /* Opera 4-6 */
|
||||||
white-space: -o-pre-wrap; /* Opera 7 */
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||||
|
|
||||||
/* placeholder styles do not work correctly when groupped into one block */
|
/* placeholder styles do not work correctly when groupped into one block */
|
||||||
|
|
||||||
@ -98,14 +93,16 @@ const StyledTextarea = styled(Textarea)`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
${props => props.trezorAction && css`
|
${props =>
|
||||||
z-index: 10001; /* bigger than modal container */
|
props.trezorAction &&
|
||||||
border-color: ${colors.WHITE};
|
css`
|
||||||
border-width: 2px;
|
z-index: 10001; /* bigger than modal container */
|
||||||
transform: translate(-1px, -1px);
|
border-color: ${colors.WHITE};
|
||||||
background: ${colors.DIVIDER};
|
border-width: 2px;
|
||||||
pointer-events: none;
|
transform: translate(-1px, -1px);
|
||||||
`}
|
background: ${colors.DIVIDER};
|
||||||
|
pointer-events: none;
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TopLabel = styled.span`
|
const TopLabel = styled.span`
|
||||||
@ -119,7 +116,7 @@ const BottomText = styled.span`
|
|||||||
color: ${props => (props.color ? props.color : colors.TEXT_SECONDARY)};
|
color: ${props => (props.color ? props.color : colors.TEXT_SECONDARY)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getColor = (inputState) => {
|
const getColor = inputState => {
|
||||||
let color = '';
|
let color = '';
|
||||||
if (inputState === 'success') {
|
if (inputState === 'success') {
|
||||||
color = colors.SUCCESS_PRIMARY;
|
color = colors.SUCCESS_PRIMARY;
|
||||||
@ -179,9 +176,7 @@ const TextArea = ({
|
|||||||
trezorAction = null,
|
trezorAction = null,
|
||||||
}) => (
|
}) => (
|
||||||
<Wrapper className={className}>
|
<Wrapper className={className}>
|
||||||
{topLabel && (
|
{topLabel && <TopLabel>{topLabel}</TopLabel>}
|
||||||
<TopLabel>{topLabel}</TopLabel>
|
|
||||||
)}
|
|
||||||
<StyledTextarea
|
<StyledTextarea
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
@ -202,15 +197,10 @@ const TextArea = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
<TrezorAction action={trezorAction}>
|
<TrezorAction action={trezorAction}>
|
||||||
<ArrowUp />{trezorAction}
|
<ArrowUp />
|
||||||
|
{trezorAction}
|
||||||
</TrezorAction>
|
</TrezorAction>
|
||||||
{bottomText && (
|
{bottomText && <BottomText color={getColor(state)}>{bottomText}</BottomText>}
|
||||||
<BottomText
|
|
||||||
color={getColor(state)}
|
|
||||||
>
|
|
||||||
{bottomText}
|
|
||||||
</BottomText>
|
|
||||||
)}
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -45,10 +45,11 @@ const Tooltip = ({
|
|||||||
<Content maxWidth={maxWidth}>{content}</Content>
|
<Content maxWidth={maxWidth}>{content}</Content>
|
||||||
{readMoreLink && (
|
{readMoreLink && (
|
||||||
<Link href={readMoreLink}>
|
<Link href={readMoreLink}>
|
||||||
<ReadMore><FormattedMessage {...l10nCommonMessages.TR_LEARN_MORE} /></ReadMore>
|
<ReadMore>
|
||||||
|
<FormattedMessage {...l10nCommonMessages.TR_LEARN_MORE} />
|
||||||
|
</ReadMore>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -60,15 +61,9 @@ const Tooltip = ({
|
|||||||
Tooltip.propTypes = {
|
Tooltip.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
placement: PropTypes.string,
|
placement: PropTypes.string,
|
||||||
children: PropTypes.oneOfType([
|
children: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
|
||||||
PropTypes.element,
|
|
||||||
PropTypes.string,
|
|
||||||
]),
|
|
||||||
maxWidth: PropTypes.number,
|
maxWidth: PropTypes.number,
|
||||||
content: PropTypes.oneOfType([
|
content: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
|
||||||
PropTypes.element,
|
|
||||||
PropTypes.string,
|
|
||||||
]),
|
|
||||||
readMoreLink: PropTypes.string,
|
readMoreLink: PropTypes.string,
|
||||||
enterDelayMs: PropTypes.number,
|
enterDelayMs: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
import type { Messages } from 'flowtype/npm/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;
|
border: 1px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TransactionItem = ({
|
const TransactionItem = ({ tx, network }: Props) => {
|
||||||
tx,
|
|
||||||
network,
|
|
||||||
}: Props) => {
|
|
||||||
const url = `${network.explorer.tx}${tx.hash}`;
|
const url = `${network.explorer.tx}${tx.hash}`;
|
||||||
const date = typeof tx.timestamp === 'string' ? tx.timestamp : undefined; // TODO: format date
|
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 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;
|
const fee = tx.tokens && tx.type === 'send' ? `${tx.fee} ${network.symbol}` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{ date && (<Date href={url} isGray>{ date }</Date>)}
|
{date && (
|
||||||
|
<Date href={url} isGray>
|
||||||
|
{date}
|
||||||
|
</Date>
|
||||||
|
)}
|
||||||
<Addresses>
|
<Addresses>
|
||||||
{ addresses.map(addr => (<Address key={addr}>{addr}</Address>)) }
|
{addresses.map(addr => (
|
||||||
{ !tx.blockHeight && (
|
<Address key={addr}>{addr}</Address>
|
||||||
<Date href={url} isGray>Transaction hash: {tx.hash}</Date>
|
))}
|
||||||
|
{!tx.blockHeight && (
|
||||||
|
<Date href={url} isGray>
|
||||||
|
Transaction hash: {tx.hash}
|
||||||
|
</Date>
|
||||||
)}
|
)}
|
||||||
</Addresses>
|
</Addresses>
|
||||||
<Value className={tx.type}>
|
<Value className={tx.type}>
|
||||||
{amount}
|
{amount}
|
||||||
{ fee && (<Fee>{operation}{fee}</Fee>) }
|
{fee && (
|
||||||
|
<Fee>
|
||||||
|
{operation}
|
||||||
|
{fee}
|
||||||
|
</Fee>
|
||||||
|
)}
|
||||||
</Value>
|
</Value>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
@ -99,4 +124,4 @@ TransactionItem.propTypes = {
|
|||||||
network: PropTypes.object.isRequired,
|
network: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TransactionItem;
|
export default TransactionItem;
|
||||||
|
@ -42,11 +42,7 @@ class CoinLogo extends PureComponent {
|
|||||||
return logo;
|
return logo;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <Wrapper className={className}>{logo}</Wrapper>;
|
||||||
<Wrapper className={className}>
|
|
||||||
{logo}
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,12 +13,12 @@ type Props = {
|
|||||||
color?: string,
|
color?: string,
|
||||||
hoverColor?: string,
|
hoverColor?: string,
|
||||||
onClick?: any,
|
onClick?: any,
|
||||||
}
|
};
|
||||||
|
|
||||||
const SvgWrapper = styled.svg`
|
const SvgWrapper = styled.svg`
|
||||||
:hover {
|
:hover {
|
||||||
path {
|
path {
|
||||||
fill: ${props => props.hoverColor}
|
fill: ${props => props.hoverColor};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -54,11 +54,7 @@ const DeviceIcon = ({
|
|||||||
viewBox="0 0 1024 1024"
|
viewBox="0 0 1024 1024"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Path
|
<Path key={majorVersion} color={color} d={getDeviceIcon(majorVersion)} />
|
||||||
key={majorVersion}
|
|
||||||
color={color}
|
|
||||||
d={getDeviceIcon(majorVersion)}
|
|
||||||
/>
|
|
||||||
</SvgWrapper>
|
</SvgWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -71,4 +67,4 @@ DeviceIcon.propTypes = {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeviceIcon;
|
export default DeviceIcon;
|
||||||
|
@ -5,8 +5,8 @@ import styled from 'styled-components';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: string;
|
model: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
const Wrapper = styled.div``;
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import ICONS from 'config/icons';
|
|||||||
const SvgWrapper = styled.svg`
|
const SvgWrapper = styled.svg`
|
||||||
:hover {
|
:hover {
|
||||||
path {
|
path {
|
||||||
fill: ${props => props.hoverColor}
|
fill: ${props => props.hoverColor};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -46,4 +46,4 @@ Icon.propTypes = {
|
|||||||
onClick: PropTypes.func,
|
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 colors from 'config/colors';
|
||||||
import ICONS from 'config/icons';
|
import ICONS from 'config/icons';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import { FONT_SIZE, FONT_FAMILY, FONT_WEIGHT, LINE_HEIGHT, TRANSITION } from 'config/variables';
|
||||||
FONT_SIZE,
|
|
||||||
FONT_FAMILY,
|
|
||||||
FONT_WEIGHT,
|
|
||||||
LINE_HEIGHT,
|
|
||||||
TRANSITION,
|
|
||||||
} from 'config/variables';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -48,10 +42,12 @@ const StyledInput = styled.input`
|
|||||||
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|
||||||
${props => props.hasAddon && css`
|
${props =>
|
||||||
border-top-right-radius: 0;
|
props.hasAddon &&
|
||||||
border-bottom-right-radius: 0;
|
css`
|
||||||
`}
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
`}
|
||||||
|
|
||||||
border: 1px solid ${colors.DIVIDER};
|
border: 1px solid ${colors.DIVIDER};
|
||||||
border-color: ${props => props.borderColor};
|
border-color: ${props => props.borderColor};
|
||||||
@ -75,14 +71,16 @@ const StyledInput = styled.input`
|
|||||||
color: ${colors.TEXT_SECONDARY};
|
color: ${colors.TEXT_SECONDARY};
|
||||||
}
|
}
|
||||||
|
|
||||||
${props => props.trezorAction && css`
|
${props =>
|
||||||
z-index: 10001;
|
props.trezorAction &&
|
||||||
position: relative; /* bigger than modal container */
|
css`
|
||||||
border-color: ${colors.WHITE};
|
z-index: 10001;
|
||||||
border-width: 2px;
|
position: relative; /* bigger than modal container */
|
||||||
transform: translate(-1px, -1px);
|
border-color: ${colors.WHITE};
|
||||||
background: ${colors.DIVIDER};
|
border-width: 2px;
|
||||||
`};
|
transform: translate(-1px, -1px);
|
||||||
|
background: ${colors.DIVIDER};
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIcon = styled(Icon)`
|
const StyledIcon = styled(Icon)`
|
||||||
@ -99,18 +97,21 @@ const BottomText = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Overlay = styled.div`
|
const Overlay = styled.div`
|
||||||
${props => props.isPartiallyHidden && css`
|
${props =>
|
||||||
bottom: 0;
|
props.isPartiallyHidden &&
|
||||||
border: 1px solid ${colors.DIVIDER};
|
css`
|
||||||
border-radius: 2px;
|
bottom: 0;
|
||||||
position: absolute;
|
border: 1px solid ${colors.DIVIDER};
|
||||||
width: 100%;
|
border-radius: 2px;
|
||||||
height: 100%;
|
position: absolute;
|
||||||
background-image: linear-gradient(to right,
|
width: 100%;
|
||||||
rgba(0,0,0, 0) 0%,
|
height: 100%;
|
||||||
rgba(249,249,249, 1) 220px
|
background-image: linear-gradient(
|
||||||
);
|
to right,
|
||||||
`}
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(249, 249, 249, 1) 220px
|
||||||
|
);
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TrezorAction = styled.div`
|
const TrezorAction = styled.div`
|
||||||
@ -168,12 +169,8 @@ class Input extends PureComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper className={this.props.className}>
|
||||||
className={this.props.className}
|
{this.props.topLabel && <TopLabel>{this.props.topLabel}</TopLabel>}
|
||||||
>
|
|
||||||
{this.props.topLabel && (
|
|
||||||
<TopLabel>{this.props.topLabel}</TopLabel>
|
|
||||||
)}
|
|
||||||
<InputWrapper>
|
<InputWrapper>
|
||||||
<InputIconWrapper>
|
<InputIconWrapper>
|
||||||
{this.props.state && (
|
{this.props.state && (
|
||||||
@ -208,15 +205,14 @@ class Input extends PureComponent {
|
|||||||
data-lpignore="true"
|
data-lpignore="true"
|
||||||
/>
|
/>
|
||||||
<TrezorAction action={this.props.trezorAction}>
|
<TrezorAction action={this.props.trezorAction}>
|
||||||
<ArrowUp />{this.props.trezorAction}
|
<ArrowUp />
|
||||||
|
{this.props.trezorAction}
|
||||||
</TrezorAction>
|
</TrezorAction>
|
||||||
</InputIconWrapper>
|
</InputIconWrapper>
|
||||||
{this.props.sideAddons && this.props.sideAddons.map(sideAddon => sideAddon)}
|
{this.props.sideAddons && this.props.sideAddons.map(sideAddon => sideAddon)}
|
||||||
</InputWrapper>
|
</InputWrapper>
|
||||||
{this.props.bottomText && (
|
{this.props.bottomText && (
|
||||||
<BottomText
|
<BottomText color={this.getColor(this.props.state)}>
|
||||||
color={this.getColor(this.props.state)}
|
|
||||||
>
|
|
||||||
{this.props.bottomText}
|
{this.props.bottomText}
|
||||||
</BottomText>
|
</BottomText>
|
||||||
)}
|
)}
|
||||||
|
@ -33,7 +33,9 @@ type DispatchProps = {
|
|||||||
|
|
||||||
export type Props = StateProps & DispatchProps;
|
export type Props = StateProps & DispatchProps;
|
||||||
|
|
||||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
|
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (
|
||||||
|
state: State
|
||||||
|
): StateProps => ({
|
||||||
modal: state.modal,
|
modal: state.modal,
|
||||||
accounts: state.accounts,
|
accounts: state.accounts,
|
||||||
devices: state.devices,
|
devices: state.devices,
|
||||||
@ -46,12 +48,17 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
wallet: state.wallet,
|
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),
|
modalActions: bindActionCreators(ModalActions, dispatch),
|
||||||
receiveActions: bindActionCreators(ReceiveActions, dispatch),
|
receiveActions: bindActionCreators(ReceiveActions, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
// export default connect(mapStateToProps, mapDispatchToProps)(Modal);
|
// export default connect(mapStateToProps, mapDispatchToProps)(Modal);
|
||||||
export default withRouter(
|
export default withRouter(
|
||||||
connect(mapStateToProps, mapDispatchToProps)(Modal),
|
connect(
|
||||||
);
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Modal)
|
||||||
|
);
|
||||||
|
@ -63,7 +63,7 @@ type Props = {
|
|||||||
onError?: (error: any) => any,
|
onError?: (error: any) => any,
|
||||||
onCancel?: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
onCancel?: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
intl: any,
|
intl: any,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
readerLoaded: boolean,
|
readerLoaded: boolean,
|
||||||
@ -83,7 +83,7 @@ class QrModal extends Component<Props, State> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
readerLoaded: true,
|
readerLoaded: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
handleScan = (data: string) => {
|
handleScan = (data: string) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -102,7 +102,7 @@ class QrModal extends Component<Props, State> {
|
|||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
handleError = (err: any) => {
|
handleError = (err: any) => {
|
||||||
// log thrown error
|
// log thrown error
|
||||||
@ -111,8 +111,12 @@ class QrModal extends Component<Props, State> {
|
|||||||
this.props.onError(err);
|
this.props.onError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError'
|
if (
|
||||||
|| err.name === 'NotReadableError' || err.name === 'TrackStartError') {
|
err.name === 'NotAllowedError' ||
|
||||||
|
err.name === 'PermissionDeniedError' ||
|
||||||
|
err.name === 'NotReadableError' ||
|
||||||
|
err.name === 'TrackStartError'
|
||||||
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: this.props.intl.formatMessage(l10nMessages.TR_CAMERA_PERMISSION_DENIED),
|
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),
|
error: this.props.intl.formatMessage(l10nMessages.TR_UNKOWN_ERROR_SEE_CONSOLE),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
handleCancel = () => {
|
handleCancel = () => {
|
||||||
if (this.props.onCancel) {
|
if (this.props.onCancel) {
|
||||||
this.props.onCancel();
|
this.props.onCancel();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<CloseLink onClick={this.handleCancel}>
|
<CloseLink onClick={this.handleCancel}>
|
||||||
<Icon
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
size={24}
|
|
||||||
color={colors.TEXT_SECONDARY}
|
|
||||||
icon={icons.CLOSE}
|
|
||||||
/>
|
|
||||||
</CloseLink>
|
</CloseLink>
|
||||||
<Padding>
|
<Padding>
|
||||||
<H2><FormattedMessage {...l10nMessages.TR_SCAN_QR_CODE} /></H2>
|
<H2>
|
||||||
{!this.state.readerLoaded && !this.state.error && <CameraPlaceholder><FormattedMessage {...l10nMessages.TR_WAITING_FOR_CAMERA} /></CameraPlaceholder>}
|
<FormattedMessage {...l10nMessages.TR_SCAN_QR_CODE} />
|
||||||
|
</H2>
|
||||||
|
{!this.state.readerLoaded && !this.state.error && (
|
||||||
|
<CameraPlaceholder>
|
||||||
|
<FormattedMessage {...l10nMessages.TR_WAITING_FOR_CAMERA} />
|
||||||
|
</CameraPlaceholder>
|
||||||
|
)}
|
||||||
{this.state.error && (
|
{this.state.error && (
|
||||||
<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>
|
<ErrorMessage>{this.state.error.toString()}</ErrorMessage>
|
||||||
</Error>
|
</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';
|
import l10nMessages from './index.messages';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
device: TrezorDevice;
|
device: TrezorDevice,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
const Wrapper = styled.div``;
|
||||||
|
|
||||||
@ -35,5 +35,4 @@ ConfirmAction.propTypes = {
|
|||||||
device: PropTypes.object.isRequired,
|
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 ConfirmAddress = (props: Props) => {
|
||||||
const {
|
const { account, network } = props.selectedAccount;
|
||||||
account,
|
|
||||||
network,
|
|
||||||
} = props.selectedAccount;
|
|
||||||
if (!account || !network) return null;
|
if (!account || !network) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -54,9 +51,13 @@ const ConfirmAddress = (props: Props) => {
|
|||||||
</P>
|
</P>
|
||||||
</Header>
|
</Header>
|
||||||
<Content>
|
<Content>
|
||||||
<P>{ account.descriptor }</P>
|
<P>{account.descriptor}</P>
|
||||||
<Label>{ network.symbol }
|
<Label>
|
||||||
<FormattedMessage {...l10nCommonMessages.TR_ACCOUNT_HASH} values={{ number: account.index + 1 }} />
|
{network.symbol}
|
||||||
|
<FormattedMessage
|
||||||
|
{...l10nCommonMessages.TR_ACCOUNT_HASH}
|
||||||
|
values={{ number: account.index + 1 }}
|
||||||
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
</Content>
|
</Content>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
@ -67,4 +68,4 @@ ConfirmAddress.propTypes = {
|
|||||||
selectedAccount: PropTypes.object.isRequired,
|
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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onReceiveConfirmation: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onReceiveConfirmation'>;
|
onReceiveConfirmation: $ElementType<
|
||||||
device: ?TrezorDevice;
|
$ElementType<BaseProps, 'modalActions'>,
|
||||||
}
|
'onReceiveConfirmation'
|
||||||
|
>,
|
||||||
|
device: ?TrezorDevice,
|
||||||
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
max-width: 370px;
|
max-width: 370px;
|
||||||
@ -68,12 +71,19 @@ const Confirmation = (props: Props) => (
|
|||||||
</StyledLink>
|
</StyledLink>
|
||||||
<H2>Your Trezor is not backed up</H2>
|
<H2>Your Trezor is not backed up</H2>
|
||||||
<Icon size={48} color={colors.WARNING_PRIMARY} icon={icons.WARNING} />
|
<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>
|
<Row>
|
||||||
<Link href={`${getOldWalletUrl(props.device)}/?backup`} target="_self">
|
<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>
|
</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>
|
</Row>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
@ -82,4 +92,4 @@ Confirmation.propTypes = {
|
|||||||
onReceiveConfirmation: PropTypes.func.isRequired,
|
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 type { TrezorDevice, State } from 'flowtype';
|
||||||
import l10nMessages from './index.messages';
|
import l10nMessages from './index.messages';
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
device: TrezorDevice;
|
device: TrezorDevice,
|
||||||
sendForm: $ElementType<State, 'sendFormEthereum'> | $ElementType<State, 'sendFormRipple'>;
|
sendForm: $ElementType<State, 'sendFormEthereum'> | $ElementType<State, 'sendFormRipple'>,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
max-width: 390px;
|
max-width: 390px;
|
||||||
@ -62,20 +61,22 @@ const FeeLevelName = styled(StyledP)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const ConfirmSignTx = (props: Props) => {
|
const ConfirmSignTx = (props: Props) => {
|
||||||
const {
|
const { amount, address, selectedFeeLevel } = props.sendForm;
|
||||||
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 (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Header>
|
<Header>
|
||||||
<DeviceIcon device={props.device} size={60} color={colors.TEXT_SECONDARY} />
|
<DeviceIcon device={props.device} size={60} color={colors.TEXT_SECONDARY} />
|
||||||
<H3>
|
<H3>
|
||||||
<FormattedMessage {...l10nMessages.TR_CONFIRM_TRANSACTION_ON} values={{ deviceLabel: props.device.label }} />
|
<FormattedMessage
|
||||||
|
{...l10nMessages.TR_CONFIRM_TRANSACTION_ON}
|
||||||
|
values={{ deviceLabel: props.device.label }}
|
||||||
|
/>
|
||||||
</H3>
|
</H3>
|
||||||
<P isSmaller>
|
<P isSmaller>
|
||||||
<FormattedMessage {...l10nMessages.TR_DETAILS_ARE_SHOWN_ON} />
|
<FormattedMessage {...l10nMessages.TR_DETAILS_ARE_SHOWN_ON} />
|
||||||
@ -85,16 +86,16 @@ const ConfirmSignTx = (props: Props) => {
|
|||||||
<Label>
|
<Label>
|
||||||
<FormattedMessage {...l10nMessages.TR_SEND_LABEL} />
|
<FormattedMessage {...l10nMessages.TR_SEND_LABEL} />
|
||||||
</Label>
|
</Label>
|
||||||
<StyledP>{`${amount} ${currency}` }</StyledP>
|
<StyledP>{`${amount} ${currency}`}</StyledP>
|
||||||
<Label>
|
<Label>
|
||||||
<FormattedMessage {...l10nMessages.TR_TO_LABEL} />
|
<FormattedMessage {...l10nMessages.TR_TO_LABEL} />
|
||||||
</Label>
|
</Label>
|
||||||
<Address>{ address }</Address>
|
<Address>{address}</Address>
|
||||||
<Label>
|
<Label>
|
||||||
<FormattedMessage {...l10nMessages.TR_FEE_LABEL} />
|
<FormattedMessage {...l10nMessages.TR_FEE_LABEL} />
|
||||||
</Label>
|
</Label>
|
||||||
<FeeLevelName>{selectedFeeLevel.value}</FeeLevelName>
|
<FeeLevelName>{selectedFeeLevel.value}</FeeLevelName>
|
||||||
<StyledP>{ selectedFeeLevel.label }</StyledP>
|
<StyledP>{selectedFeeLevel.label}</StyledP>
|
||||||
</Content>
|
</Content>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
@ -105,4 +106,4 @@ ConfirmSignTx.propTypes = {
|
|||||||
sendForm: PropTypes.object.isRequired,
|
sendForm: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfirmSignTx;
|
export default ConfirmSignTx;
|
||||||
|
@ -14,7 +14,7 @@ const definedMessages: Messages = defineMessages({
|
|||||||
TR_TO_LABEL: {
|
TR_TO_LABEL: {
|
||||||
id: 'TR_TO_LABEL',
|
id: 'TR_TO_LABEL',
|
||||||
defaultMessage: 'To',
|
defaultMessage: 'To',
|
||||||
description: 'Label for recepeint\'s address',
|
description: "Label for recepeint's address",
|
||||||
},
|
},
|
||||||
TR_SEND_LABEL: {
|
TR_SEND_LABEL: {
|
||||||
id: '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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
device: TrezorDevice;
|
device: TrezorDevice,
|
||||||
account: $ElementType<$ElementType<BaseProps, 'selectedAccount'>, 'account'>;
|
account: $ElementType<$ElementType<BaseProps, 'selectedAccount'>, 'account'>,
|
||||||
showAddress: $ElementType<$ElementType<BaseProps, 'receiveActions'>, 'showAddress'>;
|
showAddress: $ElementType<$ElementType<BaseProps, 'receiveActions'>, 'showAddress'>,
|
||||||
showUnverifiedAddress: $ElementType<$ElementType<BaseProps, 'receiveActions'>, 'showUnverifiedAddress'>;
|
showUnverifiedAddress: $ElementType<
|
||||||
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
$ElementType<BaseProps, 'receiveActions'>,
|
||||||
}
|
'showUnverifiedAddress'
|
||||||
|
>,
|
||||||
|
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
|
};
|
||||||
|
|
||||||
const StyledLink = styled(Link)`
|
const StyledLink = styled(Link)`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -35,7 +38,6 @@ const StyledLink = styled(Link)`
|
|||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
max-width: 370px;
|
max-width: 370px;
|
||||||
padding: 30px 0px;
|
padding: 30px 0px;
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Content = styled.div`
|
const Content = styled.div`
|
||||||
@ -57,7 +59,7 @@ const Row = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
Button + Button {
|
button + button {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -119,15 +121,27 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
|
|||||||
let claim;
|
let claim;
|
||||||
|
|
||||||
if (!device.connected) {
|
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} />;
|
claim = <FormattedMessage {...l10nMessages.TR_PLEASE_CONNECT_YOUR_DEVICE} />;
|
||||||
} else {
|
} else {
|
||||||
// corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings
|
// corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings
|
||||||
const enable: boolean = !!(device.features && device.features.passphrase_protection);
|
const enable: boolean = !!(device.features && device.features.passphrase_protection);
|
||||||
deviceStatus = <FormattedMessage {...l10nMessages.TR_DEVICE_LABEL_IS_UNAVAILABLE} values={{ deviceLabel: device.label }} />;
|
deviceStatus = (
|
||||||
claim = enable
|
<FormattedMessage
|
||||||
? <FormattedMessage {...l10nMessages.TR_PLEASE_ENABLE_PASSPHRASE} />
|
{...l10nMessages.TR_DEVICE_LABEL_IS_UNAVAILABLE}
|
||||||
: <FormattedMessage {...l10nMessages.TR_PLEASE_DISABLE_PASSPHRASE} />;
|
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;
|
const needsBackup = device.features && device.features.needs_backup;
|
||||||
@ -138,7 +152,7 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
|
|||||||
<StyledLink onClick={onCancel}>
|
<StyledLink onClick={onCancel}>
|
||||||
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<H2>{ deviceStatus }</H2>
|
<H2>{deviceStatus}</H2>
|
||||||
<StyledP isSmaller>
|
<StyledP isSmaller>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
{...l10nMessages.TR_TO_PREVENT_PHISHING_ATTACKS_COMMA}
|
{...l10nMessages.TR_TO_PREVENT_PHISHING_ATTACKS_COMMA}
|
||||||
@ -148,8 +162,12 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
|
|||||||
</Content>
|
</Content>
|
||||||
<Content>
|
<Content>
|
||||||
<Row>
|
<Row>
|
||||||
<Button onClick={() => (!account ? this.verifyAddress() : 'false')}><FormattedMessage {...l10nMessages.TR_TRY_AGAIN} /></Button>
|
<Button onClick={() => (!account ? this.verifyAddress() : 'false')}>
|
||||||
<WarnButton isWhite onClick={() => this.showUnverifiedAddress()}><FormattedMessage {...l10nMessages.TR_SHOW_UNVERIFIED_ADDRESS} /></WarnButton>
|
<FormattedMessage {...l10nMessages.TR_TRY_AGAIN} />
|
||||||
|
</Button>
|
||||||
|
<WarnButton isWhite onClick={() => this.showUnverifiedAddress()}>
|
||||||
|
<FormattedMessage {...l10nMessages.TR_SHOW_UNVERIFIED_ADDRESS} />
|
||||||
|
</WarnButton>
|
||||||
</Row>
|
</Row>
|
||||||
</Content>
|
</Content>
|
||||||
{needsBackup && <Divider />}
|
{needsBackup && <Divider />}
|
||||||
@ -157,7 +175,10 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
|
|||||||
<>
|
<>
|
||||||
<Content>
|
<Content>
|
||||||
<H2>Device {device.label} is not backed up</H2>
|
<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>
|
||||||
<Content>
|
<Content>
|
||||||
<Row>
|
<Row>
|
||||||
@ -181,4 +202,4 @@ ConfirmUnverifiedAddress.propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
onCancel: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfirmUnverifiedAddress;
|
export default ConfirmUnverifiedAddress;
|
||||||
|
@ -17,11 +17,13 @@ const definedMessages: Messages = defineMessages({
|
|||||||
},
|
},
|
||||||
TR_PLEASE_ENABLE_PASSPHRASE: {
|
TR_PLEASE_ENABLE_PASSPHRASE: {
|
||||||
id: '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: {
|
TR_PLEASE_DISABLE_PASSPHRASE: {
|
||||||
id: '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: {
|
TR_SHOW_UNVERIFIED_ADDRESS: {
|
||||||
id: 'TR_SHOW_UNVERIFIED_ADDRESS',
|
id: 'TR_SHOW_UNVERIFIED_ADDRESS',
|
||||||
@ -34,8 +36,9 @@ const definedMessages: Messages = defineMessages({
|
|||||||
},
|
},
|
||||||
TR_TO_PREVENT_PHISHING_ATTACKS_COMMA: {
|
TR_TO_PREVENT_PHISHING_ATTACKS_COMMA: {
|
||||||
id: '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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
device: TrezorDevice;
|
device: TrezorDevice,
|
||||||
devices: $ElementType<BaseProps, 'devices'>;
|
devices: $ElementType<BaseProps, 'devices'>,
|
||||||
onDuplicateDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onDuplicateDevice'>;
|
onDuplicateDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onDuplicateDevice'>,
|
||||||
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
defaultName: string;
|
defaultName: string,
|
||||||
instance: number;
|
instance: number,
|
||||||
instanceName: ?string;
|
instanceName: ?string,
|
||||||
isUsed: boolean;
|
isUsed: boolean,
|
||||||
}
|
};
|
||||||
|
|
||||||
const StyledLink = styled(Link)`
|
const StyledLink = styled(Link)`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -102,14 +102,14 @@ class DuplicateDevice extends PureComponent<Props, State> {
|
|||||||
onNameChange = (value: string): void => {
|
onNameChange = (value: string): void => {
|
||||||
let isUsed: boolean = false;
|
let isUsed: boolean = false;
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
isUsed = (this.props.devices.find(d => d.instanceName === value) !== undefined);
|
isUsed = this.props.devices.find(d => d.instanceName === value) !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
instanceName: value.length > 0 ? value : null,
|
instanceName: value.length > 0 ? value : null,
|
||||||
isUsed,
|
isUsed,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
input: ?HTMLInputElement;
|
input: ?HTMLInputElement;
|
||||||
|
|
||||||
@ -123,25 +123,27 @@ class DuplicateDevice extends PureComponent<Props, State> {
|
|||||||
keyboardHandler: (event: KeyboardEvent) => void;
|
keyboardHandler: (event: KeyboardEvent) => void;
|
||||||
|
|
||||||
submit() {
|
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 });
|
this.props.onDuplicateDevice({ ...this.props.device, ...extended });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { device, onCancel } = this.props;
|
const { device, onCancel } = this.props;
|
||||||
const {
|
const { defaultName, instanceName, isUsed } = this.state;
|
||||||
defaultName,
|
|
||||||
instanceName,
|
|
||||||
isUsed,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={onCancel}>
|
<StyledLink onClick={onCancel}>
|
||||||
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<H3>Clone { device.label }?</H3>
|
<H3>Clone {device.label}?</H3>
|
||||||
<StyledP isSmaller>This will create new instance of device which can be used with different passphrase</StyledP>
|
<StyledP isSmaller>
|
||||||
|
This will create new instance of device which can be used with different
|
||||||
|
passphrase
|
||||||
|
</StyledP>
|
||||||
<Column>
|
<Column>
|
||||||
<Label>Instance name</Label>
|
<Label>Instance name</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -151,22 +153,20 @@ class DuplicateDevice extends PureComponent<Props, State> {
|
|||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
placeholder={defaultName}
|
placeholder={defaultName}
|
||||||
innerRef={(element) => { this.input = element; }}
|
innerRef={element => {
|
||||||
|
this.input = element;
|
||||||
|
}}
|
||||||
onChange={event => this.onNameChange(event.currentTarget.value)}
|
onChange={event => this.onNameChange(event.currentTarget.value)}
|
||||||
value={instanceName}
|
value={instanceName}
|
||||||
/>
|
/>
|
||||||
{ isUsed && <ErrorMessage>Instance name is already in use</ErrorMessage> }
|
{isUsed && <ErrorMessage>Instance name is already in use</ErrorMessage>}
|
||||||
</Column>
|
</Column>
|
||||||
<Column>
|
<Column>
|
||||||
<StyledButton
|
<StyledButton disabled={isUsed} onClick={() => this.submit()}>
|
||||||
disabled={isUsed}
|
Create new instance
|
||||||
onClick={() => this.submit()}
|
|
||||||
>Create new instance
|
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
<StyledButton
|
<StyledButton isWhite onClick={onCancel}>
|
||||||
isWhite
|
Cancel
|
||||||
onClick={onCancel}
|
|
||||||
>Cancel
|
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</Column>
|
</Column>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
@ -181,4 +181,4 @@ DuplicateDevice.propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
device: TrezorDevice;
|
device: TrezorDevice,
|
||||||
onForgetSingleDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onForgetSingleDevice'>;
|
onForgetSingleDevice: $ElementType<
|
||||||
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
$ElementType<BaseProps, 'modalActions'>,
|
||||||
}
|
'onForgetSingleDevice'
|
||||||
|
>,
|
||||||
|
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 360px;
|
width: 360px;
|
||||||
@ -35,7 +38,7 @@ const Row = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
Button + Button {
|
button + button {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -75,11 +78,17 @@ class ForgetDevice extends PureComponent<Props> {
|
|||||||
/>
|
/>
|
||||||
</H2>
|
</H2>
|
||||||
<StyledP isSmaller>
|
<StyledP isSmaller>
|
||||||
<FormattedMessage {...l10nMessages.TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM} />
|
<FormattedMessage
|
||||||
|
{...l10nMessages.TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM}
|
||||||
|
/>
|
||||||
</StyledP>
|
</StyledP>
|
||||||
<Row>
|
<Row>
|
||||||
<Button onClick={() => this.forget()}><FormattedMessage {...l10nCommonMessages.TR_FORGET_DEVICE} /></Button>
|
<Button onClick={() => this.forget()}>
|
||||||
<Button isWhite onClick={this.props.onCancel}><FormattedMessage {...l10nMessages.TR_DONT_FORGET} /></Button>
|
<FormattedMessage {...l10nCommonMessages.TR_FORGET_DEVICE} />
|
||||||
|
</Button>
|
||||||
|
<Button isWhite onClick={this.props.onCancel}>
|
||||||
|
<FormattedMessage {...l10nMessages.TR_DONT_FORGET} />
|
||||||
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
@ -92,4 +101,4 @@ ForgetDevice.propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
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({
|
const definedMessages: Messages = defineMessages({
|
||||||
TR_DONT_FORGET: {
|
TR_DONT_FORGET: {
|
||||||
id: 'TR_DONT_FORGET',
|
id: 'TR_DONT_FORGET',
|
||||||
defaultMessage: 'Don\'t forget',
|
defaultMessage: "Don't forget",
|
||||||
description: 'Button in remember/forget dialog',
|
description: 'Button in remember/forget dialog',
|
||||||
},
|
},
|
||||||
TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM: {
|
TR_FORGETTING_ONLY_REMOVES_THE_DEVICE_FROM: {
|
||||||
id: '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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
device: TrezorDevice;
|
device: TrezorDevice,
|
||||||
instances: ?Array<TrezorDevice>;
|
instances: ?Array<TrezorDevice>,
|
||||||
onRememberDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onRememberDevice'>;
|
onRememberDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onRememberDevice'>,
|
||||||
onForgetDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onForgetDevice'>;
|
onForgetDevice: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onForgetDevice'>,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
countdown: number;
|
countdown: number,
|
||||||
ticker?: number;
|
ticker?: number,
|
||||||
}
|
};
|
||||||
|
|
||||||
const ButtonContent = styled.div`
|
const ButtonContent = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -52,7 +52,7 @@ const Column = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
Button + Button {
|
button + button {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -120,7 +120,7 @@ class RememberDevice extends PureComponent<Props, State> {
|
|||||||
let { label } = device;
|
let { label } = device;
|
||||||
const deviceCount = instances ? instances.length : 0;
|
const deviceCount = instances ? instances.length : 0;
|
||||||
if (instances && instances.length > 0) {
|
if (instances && instances.length > 0) {
|
||||||
label = instances.map(instance => (instance.instanceLabel)).join(',');
|
label = instances.map(instance => instance.instanceLabel).join(',');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
@ -154,10 +154,7 @@ class RememberDevice extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</ButtonContent>
|
</ButtonContent>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button isWhite onClick={() => onRememberDevice(device)}>
|
||||||
isWhite
|
|
||||||
onClick={() => onRememberDevice(device)}
|
|
||||||
>
|
|
||||||
<FormattedMessage {...l10nMessages.TR_REMEMBER_DEVICE} />
|
<FormattedMessage {...l10nMessages.TR_REMEMBER_DEVICE} />
|
||||||
</Button>
|
</Button>
|
||||||
</Column>
|
</Column>
|
||||||
@ -173,4 +170,4 @@ RememberDevice.propTypes = {
|
|||||||
onForgetDevice: PropTypes.func.isRequired,
|
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({
|
const definedMessages: Messages = defineMessages({
|
||||||
TR_WOULD_YOU_LIKE_TREZOR_WALLET_TO: {
|
TR_WOULD_YOU_LIKE_TREZOR_WALLET_TO: {
|
||||||
id: '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: {
|
TR_REMEMBER_DEVICE: {
|
||||||
id: '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 = {
|
type Props = {
|
||||||
intl: any,
|
intl: any,
|
||||||
device: TrezorDevice;
|
device: TrezorDevice,
|
||||||
onWalletTypeRequest: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onWalletTypeRequest'>;
|
onWalletTypeRequest: $ElementType<
|
||||||
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
$ElementType<BaseProps, 'modalActions'>,
|
||||||
}
|
'onWalletTypeRequest'
|
||||||
|
>,
|
||||||
|
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div``;
|
||||||
`;
|
|
||||||
|
|
||||||
const Header = styled.div`
|
const Header = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -71,10 +73,12 @@ const Content = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
${props => props.isTop && css`
|
${props =>
|
||||||
padding-top: 40px;
|
props.isTop &&
|
||||||
border-bottom: 1px solid ${colors.DIVIDER};
|
css`
|
||||||
`}
|
padding-top: 40px;
|
||||||
|
border-bottom: 1px solid ${colors.DIVIDER};
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class WalletType extends PureComponent<Props> {
|
class WalletType extends PureComponent<Props> {
|
||||||
@ -105,17 +109,13 @@ class WalletType extends PureComponent<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{ device.state && (
|
{device.state && (
|
||||||
<StyledLink onClick={onCancel}>
|
<StyledLink onClick={onCancel}>
|
||||||
<Icon
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
size={24}
|
|
||||||
color={colors.TEXT_SECONDARY}
|
|
||||||
icon={icons.CLOSE}
|
|
||||||
/>
|
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
)}
|
)}
|
||||||
<StyledHeading>{ device.state
|
<StyledHeading>
|
||||||
? (
|
{device.state ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
{...l10nMessages.TR_CHANGE_WALLET_TYPE_FOR}
|
{...l10nMessages.TR_CHANGE_WALLET_TYPE_FOR}
|
||||||
values={{ deviceLabel: device.instanceLabel }}
|
values={{ deviceLabel: device.instanceLabel }}
|
||||||
@ -143,25 +143,21 @@ class WalletType extends PureComponent<Props> {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
maxWidth={285}
|
maxWidth={285}
|
||||||
placement="top"
|
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"
|
readMoreLink="https://wiki.trezor.io/Passphrase"
|
||||||
>
|
>
|
||||||
<StyledIcon
|
<StyledIcon icon={icons.HELP} color={colors.TEXT_SECONDARY} size={26} />
|
||||||
icon={icons.HELP}
|
|
||||||
color={colors.TEXT_SECONDARY}
|
|
||||||
size={26}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Header>
|
<Header>
|
||||||
<WalletTypeIcon
|
<WalletTypeIcon type="hidden" size={32} color={colors.TEXT_PRIMARY} />
|
||||||
type="hidden"
|
|
||||||
size={32}
|
|
||||||
color={colors.TEXT_PRIMARY}
|
|
||||||
/>
|
|
||||||
<FormattedMessage {...l10nMessages.TR_HIDDEN_WALLET} />
|
<FormattedMessage {...l10nMessages.TR_HIDDEN_WALLET} />
|
||||||
</Header>
|
</Header>
|
||||||
<P isSmaller>
|
<P isSmaller>
|
||||||
<FormattedMessage {...l10nMessages.TR_ASKED_ENTER_YOUR_PASSPHRASE_TO_UNLOCK} />
|
<FormattedMessage
|
||||||
|
{...l10nMessages.TR_ASKED_ENTER_YOUR_PASSPHRASE_TO_UNLOCK}
|
||||||
|
/>
|
||||||
</P>
|
</P>
|
||||||
<StyledButton isWhite onClick={() => onWalletTypeRequest(true)}>
|
<StyledButton isWhite onClick={() => onWalletTypeRequest(true)}>
|
||||||
<FormattedMessage {...l10nCommonMessages.TR_GO_TO_HIDDEN_WALLET} />
|
<FormattedMessage {...l10nCommonMessages.TR_GO_TO_HIDDEN_WALLET} />
|
||||||
@ -178,4 +174,4 @@ WalletType.propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
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: {
|
TR_PASSPHRASE_IS_OPTIONAL_FEATURE: {
|
||||||
id: '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: {
|
TR_ASKED_ENTER_YOUR_PASSPHRASE_TO_UNLOCK: {
|
||||||
id: '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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -51,11 +51,7 @@ const Img = styled.img`
|
|||||||
const CardanoWallet = (props: Props) => (
|
const CardanoWallet = (props: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={props.onCancel}>
|
<StyledLink onClick={props.onCancel}>
|
||||||
<Icon
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
size={24}
|
|
||||||
color={colors.TEXT_SECONDARY}
|
|
||||||
icon={icons.CLOSE}
|
|
||||||
/>
|
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<Img src={CardanoImage} />
|
<Img src={CardanoImage} />
|
||||||
<H2>
|
<H2>
|
||||||
@ -77,4 +73,4 @@ CardanoWallet.propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -47,11 +47,7 @@ const Img = styled.img`
|
|||||||
const NemWallet = (props: Props) => (
|
const NemWallet = (props: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={props.onCancel}>
|
<StyledLink onClick={props.onCancel}>
|
||||||
<Icon
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
size={24}
|
|
||||||
color={colors.TEXT_SECONDARY}
|
|
||||||
icon={icons.CLOSE}
|
|
||||||
/>
|
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<H2>
|
<H2>
|
||||||
<FormattedMessage {...l10nMessages.TR_NEM_WALLET} />
|
<FormattedMessage {...l10nMessages.TR_NEM_WALLET} />
|
||||||
@ -75,4 +71,4 @@ NemWallet.propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
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: {
|
TR_WE_HAVE_PARTNERED_UP_WITH_THE_NEM: {
|
||||||
id: '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: {
|
TR_MAKE_SURE_YOU_DOWNLOAD_THE_UNIVERSAL: {
|
||||||
id: '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';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
onCancel: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>,
|
||||||
}
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -51,11 +51,7 @@ const Img = styled.img`
|
|||||||
const StellarWallet = (props: Props) => (
|
const StellarWallet = (props: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={props.onCancel}>
|
<StyledLink onClick={props.onCancel}>
|
||||||
<Icon
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
size={24}
|
|
||||||
color={colors.TEXT_SECONDARY}
|
|
||||||
icon={icons.CLOSE}
|
|
||||||
/>
|
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<Img src={StellarImage} />
|
<Img src={StellarImage} />
|
||||||
<H2>
|
<H2>
|
||||||
@ -77,4 +73,4 @@ StellarWallet.propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
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