mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-24 09:18:09 +00:00
Refactoring: SelectedAccountReducer synchronized with other reducers
thru WalletService + implementation new data model in components
This commit is contained in:
parent
d74c2ceaba
commit
283af40356
@ -1,66 +1,98 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import * as ACCOUNT from './constants/account';
|
||||
import * as SEND from './constants/send';
|
||||
|
||||
import * as SendFormActions from './SendFormActions';
|
||||
import * as SessionStorageActions from './SessionStorageActions';
|
||||
import * as stateUtils from '../reducers/utils';
|
||||
|
||||
import { initialState } from '../reducers/SelectedAccountReducer';
|
||||
|
||||
import type { AsyncAction, ThunkAction, Action, GetState, Dispatch, TrezorDevice } from '~/flowtype';
|
||||
import type { State } from '../reducers/SelectedAccountReducer';
|
||||
import type { Coin } from '../reducers/LocalStorageReducer';
|
||||
import type {
|
||||
Coin,
|
||||
TrezorDevice,
|
||||
AsyncAction,
|
||||
ThunkAction,
|
||||
Action,
|
||||
GetState,
|
||||
Dispatch,
|
||||
State,
|
||||
} from '~/flowtype';
|
||||
|
||||
|
||||
export type SelectedAccountAction = {
|
||||
type: typeof ACCOUNT.INIT,
|
||||
state: State
|
||||
} | {
|
||||
type: typeof ACCOUNT.DISPOSE,
|
||||
} | {
|
||||
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
||||
payload: $ElementType<State, 'selectedAccount'>
|
||||
};
|
||||
|
||||
export const init = (): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => {
|
||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
|
||||
const { location } = getState().router;
|
||||
const urlParams = location.state;
|
||||
const locationChange: boolean = action.type === LOCATION_CHANGE;
|
||||
const state: State = getState();
|
||||
const location = state.router.location;
|
||||
|
||||
const selected: ?TrezorDevice = getState().wallet.selectedDevice;;
|
||||
if (!selected) return;
|
||||
if (!selected.state || !selected.features) return;
|
||||
let needUpdate: boolean = false;
|
||||
|
||||
const { config } = getState().localStorage;
|
||||
const coin: ?Coin = config.coins.find(c => c.network === urlParams.network);
|
||||
if (!coin) return;
|
||||
// reset form to default
|
||||
if (action.type === SEND.TX_COMPLETE) {
|
||||
dispatch( SendFormActions.init() );
|
||||
// linear action
|
||||
SessionStorageActions.clear(location.pathname);
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
index: parseInt(urlParams.account),
|
||||
deviceState: selected.state || '0',
|
||||
deviceId: selected.features ? selected.features.device_id : '0',
|
||||
deviceInstance: selected.instance,
|
||||
network: urlParams.network,
|
||||
coin,
|
||||
location: location.pathname,
|
||||
};
|
||||
// handle devices state change (from trezor-connect events or location change)
|
||||
if (locationChange
|
||||
|| prevState.accounts !== state.accounts
|
||||
|| prevState.discovery !== state.discovery
|
||||
|| prevState.tokens !== state.tokens
|
||||
|| prevState.web3 !== state.web3) {
|
||||
|
||||
const account = stateUtils.getSelectedAccount(state);
|
||||
const network = stateUtils.getSelectedNetwork(state);
|
||||
const discovery = stateUtils.getDiscoveryProcess(state);
|
||||
const tokens = stateUtils.getTokens(state, account);
|
||||
const web3 = stateUtils.getWeb3(state);
|
||||
|
||||
const payload: $ElementType<State, 'selectedAccount'> = {
|
||||
// location: location.pathname,
|
||||
account,
|
||||
network,
|
||||
discovery,
|
||||
tokens,
|
||||
web3
|
||||
}
|
||||
|
||||
let needUpdate: boolean = false;
|
||||
Object.keys(payload).forEach((key) => {
|
||||
if (payload[key] !== state.selectedAccount[key]) {
|
||||
needUpdate = true;
|
||||
}
|
||||
})
|
||||
|
||||
if (needUpdate) {
|
||||
dispatch({
|
||||
type: ACCOUNT.INIT,
|
||||
state: state
|
||||
});
|
||||
|
||||
}
|
||||
type: ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
||||
payload,
|
||||
})
|
||||
}
|
||||
|
||||
export const update = (initAccountAction: () => ThunkAction): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const {
|
||||
selectedAccount,
|
||||
router
|
||||
} = getState();
|
||||
if (locationChange) {
|
||||
|
||||
if (prevState.router.location && prevState.router.location.state.send) {
|
||||
SessionStorageActions.save(prevState.router.location.pathname, state.sendForm);
|
||||
}
|
||||
|
||||
const shouldReload: boolean = (!selectedAccount || router.location.pathname !== selectedAccount.location);
|
||||
if (shouldReload) {
|
||||
dispatch( dispose() );
|
||||
dispatch( init() );
|
||||
if (selectedAccount !== null)
|
||||
initAccountAction();
|
||||
if (location.state.send) {
|
||||
dispatch( SendFormActions.init( SessionStorageActions.load(location.pathname) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,9 +102,3 @@ export const dispose = (): Action => {
|
||||
type: ACCOUNT.DISPOSE
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
init,
|
||||
update,
|
||||
dispose
|
||||
}
|
@ -17,7 +17,7 @@ import BigNumber from 'bignumber.js';
|
||||
import { initialState } from '../reducers/SendFormReducer';
|
||||
import { findAccount } from '../reducers/AccountsReducer';
|
||||
import { findToken } from '../reducers/TokensReducer';
|
||||
import { findDevice } from '../reducers/DevicesReducer';
|
||||
import { findDevice } from '../reducers/utils';
|
||||
|
||||
import type {
|
||||
Dispatch,
|
||||
@ -34,6 +34,7 @@ import type { Config, Coin } from '../reducers/LocalStorageReducer';
|
||||
import type { Token } from '../reducers/TokensReducer';
|
||||
import type { State, FeeLevel } from '../reducers/SendFormReducer';
|
||||
import type { Account } from '../reducers/AccountsReducer';
|
||||
import type { Props } from '../components/wallet/account/send';
|
||||
|
||||
export type SendTxAction = {
|
||||
type: typeof SEND.TX_COMPLETE,
|
||||
@ -56,6 +57,9 @@ export type SendFormAction = SendTxAction | {
|
||||
errors: {[k: string]: string},
|
||||
warnings: {[k: string]: string},
|
||||
infos: {[k: string]: string}
|
||||
} | {
|
||||
type: typeof SEND.ADDRESS_VALIDATION,
|
||||
state: State
|
||||
} | {
|
||||
type: typeof SEND.ADDRESS_CHANGE,
|
||||
state: State
|
||||
@ -111,21 +115,28 @@ export type SendFormAction = SendTxAction | {
|
||||
//const numberRegExp = new RegExp('^([0-9]{0,10}\\.)?[0-9]{1,18}$');
|
||||
const numberRegExp: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$');
|
||||
|
||||
const calculateFee = (gasPrice: string, gasLimit: string): string => {
|
||||
return EthereumjsUnits.convert( new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether');
|
||||
}
|
||||
|
||||
const calculateTotal = (amount: string, gasPrice: string, gasLimit: string): string => {
|
||||
export const calculateFee = (gasPrice: string, gasLimit: string): string => {
|
||||
try {
|
||||
return new BigNumber(amount).plus( calculateFee(gasPrice, gasLimit) ).toString();
|
||||
return EthereumjsUnits.convert( new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether');
|
||||
} catch (error) {
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
|
||||
const calculateMaxAmount = (balance: string, gasPrice: string, gasLimit: string): string => {
|
||||
export const calculateTotal = (amount: string, gasPrice: string, gasLimit: string): string => {
|
||||
try {
|
||||
const fee = EthereumjsUnits.convert( new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether');
|
||||
// return new BigNumber(amount).plus( calculateFee(gasPrice, gasLimit) ).toString();
|
||||
const fee = calculateFee(gasPrice, gasLimit);
|
||||
return new BigNumber(amount).plus( calculateFee(gasPrice, gasLimit) ).toFixed(16);
|
||||
} catch (error) {
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
|
||||
export const calculateMaxAmount = (balance: string, gasPrice: string, gasLimit: string): string => {
|
||||
try {
|
||||
// TODO - minus pendings
|
||||
const fee = calculateFee(gasPrice, gasLimit);
|
||||
const b = new BigNumber(balance);
|
||||
const max = b.minus(fee);
|
||||
if (max.lessThan(0)) return '0';
|
||||
@ -136,24 +147,55 @@ const calculateMaxAmount = (balance: string, gasPrice: string, gasLimit: string)
|
||||
|
||||
}
|
||||
|
||||
const setAmountAndTotal = (state: State): State => {
|
||||
export const calculate = (prevProps: Props, props: Props) => {
|
||||
|
||||
// if (state.setMax) {
|
||||
// const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
// if (!account) return;
|
||||
const {
|
||||
account,
|
||||
} = props.selectedAccount;
|
||||
if (!account) return;
|
||||
|
||||
// if (isToken) {
|
||||
// const token: ?Token = findToken(getState().tokens, account.address, state.selectedCurrency, account.deviceState);
|
||||
// if (!token) return;
|
||||
// state.amount = token.balance;
|
||||
// } else {
|
||||
// state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||
// }
|
||||
// }
|
||||
// state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||
console.warn("CALCULATE!", props)
|
||||
|
||||
return state;
|
||||
const prevState = prevProps.sendForm;
|
||||
const state = props.sendForm;
|
||||
const isToken: boolean = state.currency !== state.networkSymbol;
|
||||
|
||||
// account balance
|
||||
// token balance
|
||||
// gasLimit, gasPrice changed
|
||||
|
||||
// const shouldRecalculateAmount =
|
||||
// (prevProps.selectedAccount.account !== account)
|
||||
// || (prevProps.)
|
||||
|
||||
|
||||
if (state.setMax) {
|
||||
|
||||
const pendingAmount = props.pending.reduce((value, p) => {
|
||||
//if (p.tx.amount)
|
||||
console.warn("PENDING AMOUNT!", p, value);
|
||||
}, 0);
|
||||
|
||||
if (isToken) {
|
||||
const token: ?Token = findToken(props.tokens, account.address, state.currency, account.deviceState);
|
||||
if (token) {
|
||||
state.amount = token.balance;
|
||||
}
|
||||
} else {
|
||||
state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||
}
|
||||
}
|
||||
|
||||
// amount changed
|
||||
// fee changed
|
||||
state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||
|
||||
if (state.selectedFeeLevel.value === 'Custom') {
|
||||
state.selectedFeeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.networkSymbol }`;
|
||||
state.selectedFeeLevel.gasPrice = state.gasPrice;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string, selected?: FeeLevel): Array<FeeLevel> => {
|
||||
const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice
|
||||
@ -194,45 +236,44 @@ export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLi
|
||||
|
||||
|
||||
// initialize component
|
||||
export const init = (): ThunkAction => {
|
||||
export const init = (stateFromStorage: ?State): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const {
|
||||
account,
|
||||
network,
|
||||
web3
|
||||
} = getState().selectedAccount;
|
||||
|
||||
const { location } = getState().router;
|
||||
const urlParams: RouterLocationState = location.state;
|
||||
if (!account || !network || !web3) return;
|
||||
|
||||
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
||||
if (!selected) return;
|
||||
|
||||
const web3instance: ?Web3Instance = getState().web3.find(w3 => w3.network === urlParams.network);
|
||||
if (!web3instance) return;
|
||||
|
||||
const account = getState().accounts.find(a => a.deviceState === accountState.deviceState && a.index === accountState.index && a.network === accountState.network);
|
||||
if (!account) return;
|
||||
if (stateFromStorage) {
|
||||
dispatch({
|
||||
type: SEND.INIT,
|
||||
state: stateFromStorage
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: check if there are some unfinished tx in localStorage
|
||||
|
||||
const coin: Coin = accountState.coin;
|
||||
|
||||
const gasPrice: BigNumber = new BigNumber( EthereumjsUnits.convert(web3instance.gasPrice, 'wei', 'gwei') ) || new BigNumber(coin.defaultGasPrice);
|
||||
const gasLimit: string = coin.defaultGasLimit.toString();
|
||||
const feeLevels: Array<FeeLevel> = getFeeLevels(coin.symbol, gasPrice, gasLimit);
|
||||
const gasPrice: BigNumber = new BigNumber( EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei') ) || new BigNumber(network.defaultGasPrice);
|
||||
const gasLimit: string = network.defaultGasLimit.toString();
|
||||
const feeLevels: Array<FeeLevel> = getFeeLevels(network.symbol, gasPrice, gasLimit);
|
||||
|
||||
// TODO: get nonce
|
||||
// TODO: LOAD DATA FROM SESSION STORAGE
|
||||
|
||||
const state: State = {
|
||||
...initialState,
|
||||
network: coin.network,
|
||||
coinSymbol: coin.symbol,
|
||||
selectedCurrency: coin.symbol,
|
||||
networkName: network.network,
|
||||
networkSymbol: network.symbol,
|
||||
currency: network.symbol,
|
||||
feeLevels,
|
||||
selectedFeeLevel: feeLevels.find(f => f.value === 'Normal'),
|
||||
recommendedGasPrice: gasPrice.toString(),
|
||||
gasLimit,
|
||||
gasPrice: gasPrice.toString(),
|
||||
nonce: account.nonce.toString(), // TODO!!!
|
||||
};
|
||||
|
||||
dispatch({
|
||||
@ -242,25 +283,76 @@ export const init = (): ThunkAction => {
|
||||
}
|
||||
}
|
||||
|
||||
export const dispose = (): Action => {
|
||||
return {
|
||||
type: SEND.DISPOSE
|
||||
}
|
||||
}
|
||||
|
||||
export const toggleAdvanced = (address: string): Action => {
|
||||
return {
|
||||
type: SEND.TOGGLE_ADVANCED
|
||||
}
|
||||
}
|
||||
|
||||
export const validation = (): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const addressValidation = (): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const {
|
||||
account,
|
||||
network,
|
||||
tokens,
|
||||
} = getState().selectedAccount;
|
||||
if (!account || !network) return;
|
||||
|
||||
const state: State = getState().sendForm;
|
||||
const infos = { ...state.infos };
|
||||
const warnings = { ...state.warnings };
|
||||
|
||||
|
||||
if (state.untouched || !state.touched.address) return;
|
||||
|
||||
const savedAccounts = getState().accounts.filter(a => a.address.toLowerCase() === state.address.toLowerCase());
|
||||
if (savedAccounts.length > 0) {
|
||||
// check if found account belongs to this network
|
||||
// corner-case: when same derivation path is used on different networks
|
||||
const currentNetworkAccount = savedAccounts.find(a => a.network === network.network);
|
||||
if (currentNetworkAccount) {
|
||||
const device: ?TrezorDevice = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState);
|
||||
if (device) {
|
||||
infos.address = `${ device.instanceLabel } Account #${ (currentNetworkAccount.index + 1) }`;
|
||||
}
|
||||
} else {
|
||||
const otherNetworkAccount = savedAccounts[0];
|
||||
const device: ?TrezorDevice = findDevice(getState().devices, otherNetworkAccount.deviceID, otherNetworkAccount.deviceState);
|
||||
const coins = getState().localStorage.config.coins;
|
||||
const otherNetwork: ?Coin = coins.find(c => c.network === otherNetworkAccount.network)
|
||||
if (device && otherNetwork) {
|
||||
warnings.address = `Looks like it's ${ device.instanceLabel } Account #${ (otherNetworkAccount.index + 1) } address of ${ otherNetwork.name } network`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete warnings.address;
|
||||
delete infos.address;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SEND.ADDRESS_VALIDATION,
|
||||
state: {
|
||||
...state,
|
||||
infos,
|
||||
warnings
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const validation = (props: Props): void => {
|
||||
|
||||
const {
|
||||
account,
|
||||
network,
|
||||
tokens,
|
||||
} = props.selectedAccount;
|
||||
if (!account || !network) return;
|
||||
|
||||
|
||||
const state: State = props.sendForm;
|
||||
|
||||
const errors: {[k: string]: string} = {};
|
||||
const warnings: {[k: string]: string} = {};
|
||||
@ -269,26 +361,17 @@ export const validation = (): ThunkAction => {
|
||||
if (state.untouched) return;
|
||||
// valid address
|
||||
if (state.touched.address) {
|
||||
|
||||
const accounts = getState().accounts;
|
||||
const savedAccounts = accounts.filter(a => a.address.toLowerCase() === state.address.toLowerCase());
|
||||
|
||||
if (state.address.length < 1) {
|
||||
errors.address = 'Address is not set';
|
||||
} else if (!EthereumjsUtil.isValidAddress(state.address)) {
|
||||
errors.address = 'Address is not valid';
|
||||
} else if (savedAccounts.length > 0) {
|
||||
// check if founded account belongs to this network
|
||||
// corner-case: when same derivation path is used on different networks
|
||||
const currentNetworkAccount = savedAccounts.find(a => a.network === accountState.network);
|
||||
if (currentNetworkAccount) {
|
||||
const device: ?TrezorDevice = findDevice(getState().devices, currentNetworkAccount.deviceID, currentNetworkAccount.deviceState);
|
||||
if (!device) return;
|
||||
infos.address = `${ device.instanceLabel } Account #${ (currentNetworkAccount.index + 1) }`;
|
||||
} else {
|
||||
const device: ?TrezorDevice = findDevice(getState().devices, savedAccounts[0].deviceID, savedAccounts[0].deviceState);
|
||||
if (!device) return;
|
||||
warnings.address = `Looks like it's ${ device.instanceLabel } Account #${ (savedAccounts[0].index + 1) } address of ${ savedAccounts[0].network.toUpperCase() } network`;
|
||||
// address warning or info are set in addressValidation ThunkAction
|
||||
// do not override this
|
||||
if (state.warnings.address) {
|
||||
warnings.address = state.warnings.address;
|
||||
} else if (state.infos.address) {
|
||||
infos.address = state.infos.address;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -303,13 +386,10 @@ export const validation = (): ThunkAction => {
|
||||
errors.amount = 'Amount is not a number';
|
||||
} else {
|
||||
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) return;
|
||||
|
||||
let decimalRegExp: RegExp;
|
||||
|
||||
if (state.selectedCurrency !== state.coinSymbol) {
|
||||
const token = findToken(getState().tokens, account.address, state.selectedCurrency, account.deviceState);
|
||||
if (state.currency !== state.networkSymbol) {
|
||||
const token = findToken(tokens, account.address, state.currency, account.deviceState);
|
||||
if (token) {
|
||||
if (parseInt(token.decimals) > 0) {
|
||||
//decimalRegExp = new RegExp('^(0|0\\.([0-9]{0,' + token.decimals + '})?|[1-9]+\\.?([0-9]{0,' + token.decimals + '})?|\\.[0-9]{1,' + token.decimals + '})$');
|
||||
@ -322,7 +402,7 @@ export const validation = (): ThunkAction => {
|
||||
if (!state.amount.match(decimalRegExp)) {
|
||||
errors.amount = `Maximum ${ token.decimals} decimals allowed`;
|
||||
} else if (new BigNumber(state.total).greaterThan(account.balance)) {
|
||||
errors.amount = `Not enough ${ state.coinSymbol } to cover transaction fee`;
|
||||
errors.amount = `Not enough ${ state.networkSymbol } to cover transaction fee`;
|
||||
} else if (new BigNumber(state.amount).greaterThan(token.balance)) {
|
||||
errors.amount = 'Not enough funds';
|
||||
} else if (new BigNumber(state.amount).lessThanOrEqualTo('0')) {
|
||||
@ -351,7 +431,7 @@ export const validation = (): ThunkAction => {
|
||||
const gl: BigNumber = new BigNumber(state.gasLimit);
|
||||
if (gl.lessThan(1)) {
|
||||
errors.gasLimit = 'Gas limit is too low';
|
||||
} else if (gl.lessThan( state.selectedCurrency !== state.coinSymbol ? accountState.coin.defaultGasLimitTokens : accountState.coin.defaultGasLimit )) {
|
||||
} else if (gl.lessThan( state.currency !== state.networkSymbol ? network.defaultGasLimitTokens : network.defaultGasLimit )) {
|
||||
warnings.gasLimit = 'Gas limit is below recommended';
|
||||
}
|
||||
}
|
||||
@ -381,9 +461,6 @@ export const validation = (): ThunkAction => {
|
||||
} else if (!state.nonce.match(re)) {
|
||||
errors.nonce = 'Nonce is not a valid number';
|
||||
} else {
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) return;
|
||||
|
||||
const n: BigNumber = new BigNumber(state.nonce);
|
||||
if (n.lessThan(account.nonce)) {
|
||||
warnings.nonce = 'Nonce is lower than recommended';
|
||||
@ -403,108 +480,75 @@ export const validation = (): ThunkAction => {
|
||||
|
||||
// valid nonce?
|
||||
|
||||
dispatch({
|
||||
type: SEND.VALIDATION,
|
||||
errors,
|
||||
warnings,
|
||||
infos
|
||||
});
|
||||
}
|
||||
state.errors = errors;
|
||||
state.warnings = warnings;
|
||||
state.infos = infos;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const onAddressChange = (address: string): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
|
||||
const currentState: State = getState().sendForm;
|
||||
const touched = { ...currentState.touched };
|
||||
const state: State = getState().sendForm;
|
||||
const touched = { ...state.touched };
|
||||
touched.address = true;
|
||||
|
||||
const state: State = {
|
||||
...currentState,
|
||||
untouched: false,
|
||||
touched,
|
||||
address
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: SEND.ADDRESS_CHANGE,
|
||||
state
|
||||
state: {
|
||||
...state,
|
||||
untouched: false,
|
||||
touched,
|
||||
address
|
||||
}
|
||||
});
|
||||
dispatch( validation() );
|
||||
|
||||
dispatch( addressValidation() );
|
||||
}
|
||||
}
|
||||
|
||||
export const onAmountChange = (amount: string): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||
const touched = { ...currentState.touched };
|
||||
const state = getState().sendForm;
|
||||
const touched = { ...state.touched };
|
||||
touched.amount = true;
|
||||
const total: string = calculateTotal(isToken ? '0' : amount, currentState.gasPrice, currentState.gasLimit);
|
||||
|
||||
const state: State = {
|
||||
...currentState,
|
||||
dispatch({
|
||||
type: SEND.AMOUNT_CHANGE,
|
||||
state: {
|
||||
...state,
|
||||
untouched: false,
|
||||
touched,
|
||||
setMax: false,
|
||||
amount,
|
||||
total
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: SEND.AMOUNT_CHANGE,
|
||||
state
|
||||
}
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
export const onCurrencyChange = (currency: { value: string, label: string }): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const {
|
||||
account,
|
||||
network
|
||||
} = getState().selectedAccount;
|
||||
if (!account || !network) return;
|
||||
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currency.value !== currentState.coinSymbol;
|
||||
const isToken: boolean = currency.value !== currentState.networkSymbol;
|
||||
const gasLimit: string = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString();
|
||||
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) {
|
||||
// account not found
|
||||
return;
|
||||
}
|
||||
|
||||
const coin: Coin = accountState.coin;
|
||||
|
||||
let gasLimit: string = '';
|
||||
let amount: string = currentState.amount;
|
||||
let total: string;
|
||||
if (isToken) {
|
||||
gasLimit = coin.defaultGasLimitTokens.toString();
|
||||
if (currentState.setMax) {
|
||||
const token: ?Token = findToken(getState().tokens, account.address, currency.value, accountState.deviceState);
|
||||
if (!token) return;
|
||||
amount = token.balance;
|
||||
}
|
||||
total = calculateTotal('0', currentState.gasPrice, currentState.gasLimit);
|
||||
} else {
|
||||
gasLimit = coin.defaultGasLimit.toString();
|
||||
if (currentState.setMax) {
|
||||
amount = calculateMaxAmount(account.balance, currentState.gasPrice, currentState.gasLimit);
|
||||
}
|
||||
total = calculateTotal(amount, currentState.gasPrice, currentState.gasLimit);
|
||||
}
|
||||
|
||||
const feeLevels: Array<FeeLevel> = getFeeLevels(currentState.coinSymbol, currentState.gasPrice, gasLimit, currentState.selectedFeeLevel);
|
||||
const feeLevels: Array<FeeLevel> = getFeeLevels(network.symbol, currentState.recommendedGasPrice, gasLimit, currentState.selectedFeeLevel);
|
||||
const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value);
|
||||
if (!selectedFeeLevel) return;
|
||||
|
||||
const state: State = {
|
||||
...currentState,
|
||||
selectedCurrency: currency.value,
|
||||
amount,
|
||||
total,
|
||||
currency: currency.value,
|
||||
// amount,
|
||||
// total,
|
||||
feeLevels,
|
||||
selectedFeeLevel,
|
||||
gasLimit,
|
||||
@ -514,66 +558,36 @@ export const onCurrencyChange = (currency: { value: string, label: string }): Th
|
||||
type: SEND.CURRENCY_CHANGE,
|
||||
state
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const onSetMax = (): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||
const touched = { ...currentState.touched };
|
||||
const state = getState().sendForm;
|
||||
const touched = { ...state.touched };
|
||||
touched.amount = true;
|
||||
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) {
|
||||
// account not found
|
||||
return;
|
||||
}
|
||||
|
||||
let amount: string = currentState.amount;
|
||||
let total: string = currentState.total;
|
||||
if (!currentState.setMax) {
|
||||
if (isToken) {
|
||||
const token: ?Token = findToken(getState().tokens, account.address, currentState.selectedCurrency, accountState.deviceState);
|
||||
if (!token) return;
|
||||
amount = token.balance;
|
||||
total = calculateTotal('0', currentState.gasPrice, currentState.gasLimit);
|
||||
} else {
|
||||
amount = calculateMaxAmount(account.balance, currentState.gasPrice, currentState.gasLimit);
|
||||
total = calculateTotal(amount, currentState.gasPrice, currentState.gasLimit);
|
||||
}
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
...currentState,
|
||||
untouched: false,
|
||||
touched,
|
||||
setMax: !currentState.setMax,
|
||||
amount,
|
||||
total
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: SEND.SET_MAX,
|
||||
state
|
||||
state: {
|
||||
...state,
|
||||
untouched: false,
|
||||
touched,
|
||||
setMax: !state.setMax,
|
||||
}
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||
const {
|
||||
network
|
||||
} = getState().selectedAccount;
|
||||
if (!network) return;
|
||||
|
||||
const coin: Coin = accountState.coin;
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
||||
|
||||
const state: State = {
|
||||
...currentState,
|
||||
@ -584,38 +598,23 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
|
||||
if (feeLevel.value === 'Custom') {
|
||||
state.advanced = true;
|
||||
feeLevel.gasPrice = state.gasPrice;
|
||||
feeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.coinSymbol }`;
|
||||
feeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.networkSymbol }`;
|
||||
} else {
|
||||
const customLevel: ?FeeLevel = state.feeLevels.find(f => f.value === 'Custom');
|
||||
if (customLevel)
|
||||
customLevel.label = '';
|
||||
state.gasPrice = feeLevel.gasPrice;
|
||||
if (isToken) {
|
||||
state.gasLimit = coin.defaultGasLimitTokens.toString()
|
||||
state.gasLimit = network.defaultGasLimitTokens.toString()
|
||||
} else {
|
||||
state.gasLimit = state.data.length > 0 ? state.gasLimit : coin.defaultGasLimit.toString();
|
||||
state.gasLimit = state.data.length > 0 ? state.gasLimit : network.defaultGasLimit.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentState.setMax) {
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) return;
|
||||
|
||||
if (isToken) {
|
||||
const token: ?Token = findToken(getState().tokens, account.address, currentState.selectedCurrency, accountState.deviceState);
|
||||
if (!token) return;
|
||||
state.amount = token.balance;
|
||||
} else {
|
||||
state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||
}
|
||||
}
|
||||
state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||
|
||||
dispatch({
|
||||
type: SEND.FEE_LEVEL_CHANGE,
|
||||
state
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
@ -624,12 +623,25 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
|
||||
|
||||
export const updateFeeLevels = (): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||
const {
|
||||
account,
|
||||
network
|
||||
} = getState().selectedAccount;
|
||||
if (!account || !network) return;
|
||||
|
||||
const feeLevels: Array<FeeLevel> = getFeeLevels(currentState.coinSymbol, currentState.recommendedGasPrice, currentState.gasLimit, currentState.selectedFeeLevel);
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
||||
let gasLimit: string = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString();
|
||||
|
||||
// override custom settings
|
||||
if (currentState.selectedFeeLevel.value === 'Custom') {
|
||||
// update only gasPrice
|
||||
currentState.selectedFeeLevel.gasPrice = currentState.recommendedGasPrice;
|
||||
// leave gas limit as it was
|
||||
gasLimit = currentState.gasLimit;
|
||||
}
|
||||
|
||||
const feeLevels: Array<FeeLevel> = getFeeLevels(network.symbol, currentState.recommendedGasPrice, gasLimit, currentState.selectedFeeLevel);
|
||||
const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value);
|
||||
if (!selectedFeeLevel) return;
|
||||
|
||||
@ -641,34 +653,18 @@ export const updateFeeLevels = (): ThunkAction => {
|
||||
gasPriceNeedsUpdate: false,
|
||||
};
|
||||
|
||||
if (currentState.setMax) {
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) return;
|
||||
if (isToken) {
|
||||
const token: ?Token = findToken(getState().tokens, account.address, state.selectedCurrency, accountState.deviceState);
|
||||
if (!token) return;
|
||||
const tokenBalance: string = token.balance;
|
||||
state.amount = tokenBalance;
|
||||
} else {
|
||||
state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||
}
|
||||
}
|
||||
state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||
|
||||
dispatch({
|
||||
type: SEND.UPDATE_FEE_LEVELS,
|
||||
state
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
export const onGasPriceChange = (gasPrice: string): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
||||
|
||||
const touched = { ...currentState.touched };
|
||||
touched.gasPrice = true;
|
||||
@ -680,99 +676,46 @@ export const onGasPriceChange = (gasPrice: string): ThunkAction => {
|
||||
gasPrice: gasPrice,
|
||||
};
|
||||
|
||||
if (gasPrice.match(numberRegExp) && state.gasLimit.match(numberRegExp)) {
|
||||
if (currentState.selectedFeeLevel.value !== 'Custom') {
|
||||
const customLevel = currentState.feeLevels.find(f => f.value === 'Custom');
|
||||
if (!customLevel) return;
|
||||
customLevel.gasPrice = gasPrice;
|
||||
customLevel.label = `${ calculateFee(gasPrice, state.gasLimit) } ${ state.coinSymbol }`;
|
||||
|
||||
state.selectedFeeLevel = customLevel;
|
||||
|
||||
if (currentState.setMax) {
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) return;
|
||||
if (isToken) {
|
||||
const token: ?Token = findToken(getState().tokens, account.address, state.selectedCurrency, accountState.deviceState);
|
||||
if (!token) return;
|
||||
const tokenBalance: string = token.balance;
|
||||
state.amount = tokenBalance;
|
||||
} else {
|
||||
state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||
}
|
||||
}
|
||||
|
||||
state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||
} else {
|
||||
// state.gasPrice = currentState.gasPrice;
|
||||
}
|
||||
|
||||
|
||||
|
||||
dispatch({
|
||||
type: SEND.GAS_PRICE_CHANGE,
|
||||
state
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
export const onGasLimitChange = (gasLimit: string, updateFeeLevels: boolean = false): ThunkAction => {
|
||||
return (dispatch: Dispatch, getState: GetState): void => {
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
|
||||
const currentState: State = getState().sendForm;
|
||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
||||
|
||||
const touched = { ...currentState.touched };
|
||||
touched.gasLimit = true;
|
||||
|
||||
const state: State = {
|
||||
...currentState,
|
||||
calculatingGasLimit: false,
|
||||
untouched: false,
|
||||
touched,
|
||||
gasLimit,
|
||||
};
|
||||
|
||||
if (gasLimit.match(numberRegExp) && state.gasPrice.match(numberRegExp)) {
|
||||
|
||||
if (updateFeeLevels) {
|
||||
const feeLevels: Array<FeeLevel> = getFeeLevels(state.coinSymbol, state.gasPrice, gasLimit, state.selectedFeeLevel);
|
||||
const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === state.selectedFeeLevel.value);
|
||||
if (!selectedFeeLevel) return;
|
||||
state.selectedFeeLevel = selectedFeeLevel;
|
||||
state.feeLevels = feeLevels;
|
||||
|
||||
} else {
|
||||
const customLevel: ?FeeLevel = state.feeLevels.find(f => f.value === 'Custom');
|
||||
if (currentState.selectedFeeLevel.value !== 'Custom') {
|
||||
const customLevel = currentState.feeLevels.find(f => f.value === 'Custom');
|
||||
if (!customLevel) return;
|
||||
customLevel.label = `${ calculateFee(currentState.gasPrice, gasLimit) } ${ state.coinSymbol }`;
|
||||
|
||||
state.selectedFeeLevel = customLevel;
|
||||
}
|
||||
|
||||
|
||||
if (state.setMax) {
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account) return;
|
||||
|
||||
if (isToken) {
|
||||
const token: ?Token = findToken(getState().tokens, account.address, state.selectedCurrency, accountState.deviceState);
|
||||
if (!token) return;
|
||||
const tokenBalance: string = token.balance;
|
||||
state.amount = tokenBalance;
|
||||
} else {
|
||||
state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||
|
||||
dispatch({
|
||||
type: SEND.GAS_LIMIT_CHANGE,
|
||||
state
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
@ -793,7 +736,6 @@ export const onNonceChange = (nonce: string): AsyncAction => {
|
||||
type: SEND.NONCE_CHANGE,
|
||||
state
|
||||
});
|
||||
dispatch( validation() );
|
||||
}
|
||||
}
|
||||
|
||||
@ -805,16 +747,22 @@ export const onDataChange = (data: string): AsyncAction => {
|
||||
|
||||
const state: State = {
|
||||
...currentState,
|
||||
calculatingGasLimit: true,
|
||||
untouched: false,
|
||||
touched,
|
||||
data,
|
||||
};
|
||||
|
||||
if (currentState.selectedFeeLevel.value !== 'Custom') {
|
||||
const customLevel = currentState.feeLevels.find(f => f.value === 'Custom');
|
||||
if (!customLevel) return;
|
||||
state.selectedFeeLevel = customLevel;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SEND.DATA_CHANGE,
|
||||
state
|
||||
});
|
||||
dispatch( validation() );
|
||||
|
||||
dispatch( estimateGasPrice() );
|
||||
}
|
||||
@ -824,58 +772,71 @@ export const onDataChange = (data: string): AsyncAction => {
|
||||
const estimateGasPrice = (): AsyncAction => {
|
||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const web3instance: ?Web3Instance = getState().web3.filter(w3 => w3.network === accountState.network)[0];
|
||||
if (!web3instance) return;
|
||||
const web3 = web3instance.web3;
|
||||
const currentState: State = getState().sendForm;
|
||||
const requestedData = currentState.data;
|
||||
const {
|
||||
web3,
|
||||
network
|
||||
} = getState().selectedAccount;
|
||||
if (!web3 || !network) return;
|
||||
|
||||
if (currentState.errors.data) {
|
||||
const w3 = web3.web3;
|
||||
|
||||
const state: State = getState().sendForm;
|
||||
const requestedData = state.data;
|
||||
|
||||
const re = /^[0-9A-Fa-f]+$/g;
|
||||
if (!re.test(requestedData)) {
|
||||
// to stop calculating
|
||||
dispatch( onGasLimitChange(state.gasLimit) );
|
||||
return;
|
||||
}
|
||||
|
||||
const data: string = '0x' + (currentState.data.length % 2 === 0 ? currentState.data : '0' + currentState.data);
|
||||
const gasLimit = await estimateGas(web3instance.web3, {
|
||||
if (state.data.length < 1) {
|
||||
// set default
|
||||
dispatch( onGasLimitChange(network.defaultGasLimit.toString()) );
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: allow data starting with 0x ...
|
||||
const data: string = '0x' + (state.data.length % 2 === 0 ? state.data : '0' + state.data);
|
||||
const gasLimit = await estimateGas(w3, {
|
||||
to: '0x0000000000000000000000000000000000000000',
|
||||
data,
|
||||
value: web3.toHex(web3.toWei(currentState.amount, 'ether')),
|
||||
gasPrice: web3.toHex( EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei') ),
|
||||
value: w3.toHex(w3.toWei(state.amount, 'ether')),
|
||||
gasPrice: w3.toHex( EthereumjsUnits.convert(state.gasPrice, 'gwei', 'wei') ),
|
||||
});
|
||||
|
||||
if (getState().sendForm.data === requestedData) {
|
||||
dispatch( onGasLimitChange(gasLimit.toString(), true) );
|
||||
dispatch( onGasLimitChange(gasLimit.toString()) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const onSend = (): AsyncAction => {
|
||||
//return onSendERC20();
|
||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
|
||||
const accountState: ?AccountState = getState().selectedAccount;
|
||||
if (!accountState) return;
|
||||
const {
|
||||
account,
|
||||
network,
|
||||
web3
|
||||
} = getState().selectedAccount;
|
||||
if (!account || !web3 || !network) return;
|
||||
|
||||
const currentState: State = getState().sendForm;
|
||||
const web3instance: ?Web3Instance = getState().web3.filter(w3 => w3.network === accountState.network)[0];
|
||||
const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||
if (!account || !web3instance) return;
|
||||
|
||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||
const web3 = web3instance.web3;
|
||||
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
||||
const w3 = web3.web3;
|
||||
|
||||
const address_n = account.addressPath;
|
||||
|
||||
let data: string = '0x' + currentState.data;
|
||||
let txAmount: string = web3.toHex(web3.toWei(currentState.amount, 'ether'));
|
||||
let txAmount: string = w3.toHex(w3.toWei(currentState.amount, 'ether'));
|
||||
let txAddress: string = currentState.address;
|
||||
if (isToken) {
|
||||
const token: ?Token = findToken(getState().tokens, account.address, currentState.selectedCurrency, accountState.deviceState);
|
||||
const token: ?Token = findToken(getState().tokens, account.address, currentState.currency, account.deviceState);
|
||||
if (!token) return;
|
||||
|
||||
const contract = web3instance.erc20.at(token.address);
|
||||
const contract = web3.erc20.at(token.address);
|
||||
const amountValue: string = new BigNumber(currentState.amount).times( Math.pow(10, token.decimals) ).toString();
|
||||
|
||||
data = contract.transfer.getData(currentState.address, amountValue, {
|
||||
@ -893,11 +854,10 @@ export const onSend = (): AsyncAction => {
|
||||
to: txAddress,
|
||||
value: txAmount,
|
||||
data,
|
||||
//chainId: 3 // ropsten
|
||||
chainId: web3instance.chainId,
|
||||
nonce: web3.toHex(account.nonce),
|
||||
gasLimit: web3.toHex(currentState.gasLimit),
|
||||
gasPrice: web3.toHex( EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei') ),
|
||||
chainId: web3.chainId,
|
||||
nonce: w3.toHex(account.nonce),
|
||||
gasLimit: w3.toHex(currentState.gasLimit),
|
||||
gasPrice: w3.toHex( EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei') ),
|
||||
r: '',
|
||||
s: '',
|
||||
v: ''
|
||||
@ -940,20 +900,19 @@ export const onSend = (): AsyncAction => {
|
||||
|
||||
txData.r = '0x' + signedTransaction.payload.r;
|
||||
txData.s = '0x' + signedTransaction.payload.s;
|
||||
txData.v = web3.toHex(signedTransaction.payload.v);
|
||||
txData.v = w3.toHex(signedTransaction.payload.v);
|
||||
|
||||
|
||||
|
||||
try {
|
||||
const tx = new EthereumjsTx(txData);
|
||||
const serializedTx = '0x' + tx.serialize().toString('hex');
|
||||
const txid: string = await pushTx(web3, serializedTx);
|
||||
const coin: Coin = accountState.coin;
|
||||
const txid: string = await pushTx(w3, serializedTx);
|
||||
|
||||
dispatch({
|
||||
type: SEND.TX_COMPLETE,
|
||||
account: account,
|
||||
selectedCurrency: currentState.selectedCurrency,
|
||||
selectedCurrency: currentState.currency,
|
||||
amount: currentState.amount,
|
||||
txid,
|
||||
txData,
|
||||
@ -964,7 +923,7 @@ export const onSend = (): AsyncAction => {
|
||||
payload: {
|
||||
type: 'success',
|
||||
title: 'Transaction success',
|
||||
message: `<a href="${coin.explorer.tx}${txid}" class="green" target="_blank" rel="noreferrer noopener">See transaction detail</a>`,
|
||||
message: `<a href="${network.explorer.tx}${txid}" class="green" target="_blank" rel="noreferrer noopener">See transaction detail</a>`,
|
||||
cancelable: true,
|
||||
actions: []
|
||||
}
|
||||
@ -987,9 +946,6 @@ export const onSend = (): AsyncAction => {
|
||||
}
|
||||
|
||||
export default {
|
||||
init,
|
||||
dispose,
|
||||
|
||||
toggleAdvanced,
|
||||
onAddressChange,
|
||||
onAmountChange,
|
||||
|
@ -1,9 +1,27 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import * as WALLET from './constants/wallet';
|
||||
import * as stateUtils from '../reducers/utils';
|
||||
|
||||
import type { TrezorDevice, RouterLocationState, ThunkAction, Dispatch, GetState } from '~/flowtype';
|
||||
import type
|
||||
{
|
||||
Account,
|
||||
Coin,
|
||||
Discovery,
|
||||
Token,
|
||||
Web3Instance,
|
||||
|
||||
TrezorDevice,
|
||||
RouterLocationState,
|
||||
ThunkAction,
|
||||
AsyncAction,
|
||||
Action,
|
||||
Dispatch,
|
||||
GetState,
|
||||
State
|
||||
} from '~/flowtype';
|
||||
|
||||
export type WalletAction = {
|
||||
type: typeof WALLET.SET_INITIAL_URL,
|
||||
@ -51,3 +69,30 @@ export const toggleDeviceDropdown = (opened: boolean): WalletAction => {
|
||||
opened
|
||||
}
|
||||
}
|
||||
|
||||
export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => {
|
||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
|
||||
const locationChange: boolean = action.type === LOCATION_CHANGE;
|
||||
const state: State = getState();
|
||||
|
||||
// handle devices state change (from trezor-connect events or location change)
|
||||
if (locationChange || prevState.devices !== state.devices) {
|
||||
const device = stateUtils.getSelectedDevice(state);
|
||||
if (state.wallet.selectedDevice !== device) {
|
||||
if (device && stateUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
|
||||
dispatch({
|
||||
type: WALLET.UPDATE_SELECTED_DEVICE,
|
||||
device
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: WALLET.SET_SELECTED_DEVICE,
|
||||
device
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -9,3 +9,5 @@ export const REMOVE: 'account__remove' = 'account__remove';
|
||||
export const SET_BALANCE: 'account__set_balance' = 'account__set_balance';
|
||||
export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce';
|
||||
export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage';
|
||||
|
||||
export const UPDATE_SELECTED_ACCOUNT: 'account__update_selected_account' = 'account__update_selected_account';
|
@ -4,6 +4,7 @@
|
||||
export const INIT: 'send__init' = 'send__init';
|
||||
export const DISPOSE: 'send__dispose' = 'send__dispose';
|
||||
export const VALIDATION: 'send__validation' = 'send__validation';
|
||||
export const ADDRESS_VALIDATION: 'send__address_validation' = 'send__address_validation';
|
||||
export const ADDRESS_CHANGE: 'send__address_change' = 'send__address_change';
|
||||
export const AMOUNT_CHANGE: 'send__amount_change' = 'send__amount_change';
|
||||
export const SET_MAX: 'send__set_max' = 'send__set_max';
|
||||
|
@ -8,10 +8,11 @@ import type { Props } from './index';
|
||||
|
||||
const ConfirmAddress = (props: Props) => {
|
||||
|
||||
const { accounts, selectedAccount } = props;
|
||||
if (!selectedAccount) return null;
|
||||
const account = findAccount(accounts, selectedAccount.index, selectedAccount.deviceState, selectedAccount.network);
|
||||
if (!account) return null;
|
||||
const {
|
||||
account,
|
||||
network
|
||||
} = props.selectedAccount;
|
||||
if (!account || !network) return null;
|
||||
|
||||
return (
|
||||
<div className="confirm-address">
|
||||
@ -21,7 +22,7 @@ const ConfirmAddress = (props: Props) => {
|
||||
</div>
|
||||
<div className="content">
|
||||
<p>{ account.address }</p>
|
||||
<label>{ selectedAccount.coin.symbol } account #{ (account.index + 1) }</label>
|
||||
<label>{ network.symbol } account #{ (account.index + 1) }</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -50,14 +51,9 @@ export class ConfirmUnverifiedAddress extends Component<Props> {
|
||||
|
||||
verifyAddress() {
|
||||
if (!this.props.modal.opened) return;
|
||||
|
||||
const {
|
||||
accounts,
|
||||
selectedAccount
|
||||
} = this.props;
|
||||
|
||||
if(!selectedAccount) return null;
|
||||
const account = findAccount(accounts, selectedAccount.index, selectedAccount.deviceState, selectedAccount.network);
|
||||
account
|
||||
} = this.props.selectedAccount;
|
||||
if (!account) return null;
|
||||
|
||||
this.props.modalActions.onCancel();
|
||||
|
@ -12,9 +12,7 @@ const Confirmation = (props: Props) => {
|
||||
const {
|
||||
amount,
|
||||
address,
|
||||
network,
|
||||
coinSymbol,
|
||||
selectedCurrency,
|
||||
currency,
|
||||
total,
|
||||
selectedFeeLevel
|
||||
} = props.sendForm;
|
||||
@ -27,7 +25,7 @@ const Confirmation = (props: Props) => {
|
||||
</div>
|
||||
<div className="content">
|
||||
<label>Send </label>
|
||||
<p>{ `${amount} ${ selectedCurrency }` }</p>
|
||||
<p>{ `${amount} ${ currency }` }</p>
|
||||
<label>To</label>
|
||||
<p>{ address }</p>
|
||||
<label>Fee</label>
|
||||
|
@ -1,115 +1,42 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Notification } from '~/js/components/common/Notification';
|
||||
import { findDevice } from '~/js/reducers/DevicesReducer';
|
||||
|
||||
// import * as SelectedAccountActions from '~/js/actions/SelectedAccountActions';
|
||||
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
|
||||
|
||||
import type { State, TrezorDevice, Action, ThunkAction } from '~/flowtype';
|
||||
import type { Account } from '~/js/reducers/AccountsReducer';
|
||||
import type { Discovery } from '~/js/reducers/DiscoveryReducer';
|
||||
|
||||
export type StateProps = {
|
||||
className: string;
|
||||
selectedAccount: $ElementType<State, 'selectedAccount'>,
|
||||
devices: $ElementType<State, 'devices'>,
|
||||
discovery: $ElementType<State, 'discovery'>,
|
||||
accounts: $ElementType<State, 'accounts'>,
|
||||
wallet: $ElementType<State, 'wallet'>,
|
||||
children?: React.Node
|
||||
}
|
||||
|
||||
export type DispatchProps = {
|
||||
selectedAccountActions: typeof SelectedAccountActions,
|
||||
initAccount: () => ThunkAction,
|
||||
disposeAccount: () => Action,
|
||||
|
||||
}
|
||||
|
||||
export type Props = StateProps & DispatchProps;
|
||||
|
||||
export type ComponentState = {
|
||||
device: ?TrezorDevice;
|
||||
account: ?Account;
|
||||
discovery: ?Discovery;
|
||||
deviceStatusNotification: ?React$Element<typeof Notification>;
|
||||
}
|
||||
|
||||
export default class SelectedAccount<P> extends Component<Props & P, ComponentState> {
|
||||
|
||||
state: ComponentState = {
|
||||
device: null,
|
||||
account: null,
|
||||
discovery: null,
|
||||
deviceStatusNotification: null
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.selectedAccountActions.init();
|
||||
this.props.initAccount();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props: Props & P) {
|
||||
|
||||
this.props.selectedAccountActions.update( this.props.initAccount );
|
||||
|
||||
const accountState = props.selectedAccount;
|
||||
if (!accountState) return;
|
||||
|
||||
const device = findDevice(props.devices, accountState.deviceId, accountState.deviceState, accountState.deviceInstance);
|
||||
if (!device) return;
|
||||
const discovery = props.discovery.find(d => d.deviceState === device.state && d.network === accountState.network);
|
||||
// if (!discovery) return;
|
||||
const account = props.accounts.find(a => a.deviceState === accountState.deviceState && a.index === accountState.index && a.network === accountState.network);
|
||||
|
||||
let deviceStatusNotification: ?React$Element<typeof Notification> = null;
|
||||
if (account) {
|
||||
if (!device.connected) {
|
||||
deviceStatusNotification = <Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />;
|
||||
} else if (!device.available) {
|
||||
deviceStatusNotification = <Notification className="info" title={ `Device ${ device.instanceLabel } is unavailable` } message="Change passphrase settings to use this device" />;
|
||||
}
|
||||
}
|
||||
|
||||
if (discovery && !discovery.completed && !deviceStatusNotification) {
|
||||
deviceStatusNotification = <Notification className="info" title="Loading accounts..." />;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
device,
|
||||
discovery,
|
||||
account,
|
||||
deviceStatusNotification
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.selectedAccountActions.dispose();
|
||||
this.props.disposeAccount();
|
||||
}
|
||||
|
||||
render(): ?React$Element<string> {
|
||||
|
||||
const props = this.props;
|
||||
const accountState = props.selectedAccount;
|
||||
|
||||
if (!accountState) {
|
||||
const SelectedAccount = (props: Props) => {
|
||||
const device = props.wallet.selectedDevice;
|
||||
if (!device || !device.state) {
|
||||
return (<section><Notification className="info" title="Loading device..." /></section>);
|
||||
}
|
||||
|
||||
const accountState = props.selectedAccount;
|
||||
|
||||
const {
|
||||
device,
|
||||
account,
|
||||
discovery
|
||||
} = this.state;
|
||||
} = accountState;
|
||||
|
||||
if (!device) {
|
||||
return (<section><Notification className="warning" title={ `Device with state ${accountState.deviceState} not found` } /></section>);
|
||||
}
|
||||
|
||||
// account not found. checking why...
|
||||
// account not found (yet). checking why...
|
||||
if (!account) {
|
||||
if (!discovery || discovery.waitingForDevice) {
|
||||
|
||||
if (device.connected) {
|
||||
// case 1: device is connected but discovery not started yet (probably waiting for auth)
|
||||
if (device.available) {
|
||||
@ -160,12 +87,31 @@ export default class SelectedAccount<P> extends Component<Props & P, ComponentSt
|
||||
// case 6: discovery is not completed yet
|
||||
return (
|
||||
<section>
|
||||
<Notification className="info" title="Account is loading..." />
|
||||
<Notification className="info" title="Loading accounts..." />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
let notification: ?React$Element<typeof Notification> = null;
|
||||
if (!device.connected) {
|
||||
notification = <Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />;
|
||||
} else if (!device.available) {
|
||||
notification = <Notification className="info" title={ `Device ${ device.instanceLabel } is unavailable` } message="Change passphrase settings to use this device" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (discovery && !discovery.completed && !notification) {
|
||||
notification = <Notification className="info" title="Loading accounts..." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={ props.className }>
|
||||
{ notification }
|
||||
{ props.children }
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SelectedAccount;
|
@ -9,31 +9,26 @@ import { QRCode } from 'react-qr-svg';
|
||||
import SelectedAccount from '../SelectedAccount';
|
||||
import { Notification } from '~/js/components/common/Notification';
|
||||
|
||||
import type { ComponentState } from '../SelectedAccount';
|
||||
import type { Props } from './index';
|
||||
|
||||
export default class Receive extends SelectedAccount<Props> {
|
||||
render() {
|
||||
return super.render() || _render(this.props, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
const _render = (props: Props, state: ComponentState): React$Element<string> => {
|
||||
|
||||
const Receive = (props: Props) => {
|
||||
|
||||
const device = props.wallet.selectedDevice;
|
||||
const {
|
||||
device,
|
||||
account,
|
||||
network,
|
||||
discovery,
|
||||
deviceStatusNotification
|
||||
} = state;
|
||||
} = props.selectedAccount;
|
||||
|
||||
if (!device || !account || !discovery) return null;
|
||||
|
||||
const {
|
||||
addressVerified,
|
||||
addressUnverified,
|
||||
} = props.receive;
|
||||
|
||||
if (!device || !account || !discovery) return <section></section>;
|
||||
|
||||
let qrCode = null;
|
||||
let address = `${account.address.substring(0, 20)}...`;
|
||||
let className = 'address hidden';
|
||||
@ -83,8 +78,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="receive">
|
||||
{ deviceStatusNotification }
|
||||
<div>
|
||||
<h2>Receive Ethereum or tokens</h2>
|
||||
|
||||
<div className={ className }>
|
||||
@ -95,6 +89,14 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
{ button }
|
||||
</div>
|
||||
{ qrCode }
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (props: Props) => {
|
||||
return (
|
||||
<SelectedAccount { ...props }>
|
||||
<Receive { ...props} />
|
||||
</SelectedAccount>
|
||||
);
|
||||
}
|
@ -6,7 +6,6 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { default as ReceiveActions } from '~/js/actions/ReceiveActions';
|
||||
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
|
||||
import * as TokenActions from '~/js/actions/TokenActions';
|
||||
import Receive from './Receive';
|
||||
|
||||
@ -28,14 +27,14 @@ type DispatchProps = BaseDispatchProps & {
|
||||
showAddress: typeof ReceiveActions.showAddress
|
||||
};
|
||||
|
||||
export type Props = StateProps & DispatchProps;
|
||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
||||
|
||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => {
|
||||
return {
|
||||
className: "receive",
|
||||
selectedAccount: state.selectedAccount,
|
||||
devices: state.devices,
|
||||
accounts: state.accounts,
|
||||
discovery: state.discovery,
|
||||
wallet: state.wallet,
|
||||
|
||||
receive: state.receive,
|
||||
modal: state.modal,
|
||||
};
|
||||
@ -43,10 +42,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
||||
|
||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => {
|
||||
return {
|
||||
selectedAccountActions: bindActionCreators(SelectedAccountActions, dispatch),
|
||||
|
||||
initAccount: bindActionCreators(ReceiveActions.init, dispatch),
|
||||
disposeAccount: bindActionCreators(ReceiveActions.dispose, dispatch),
|
||||
showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch),
|
||||
};
|
||||
}
|
||||
|
@ -14,15 +14,17 @@ type Props = {
|
||||
|
||||
const AdvancedForm = (props: Props) => {
|
||||
|
||||
const selectedAccount = props.selectedAccount;
|
||||
if (!selectedAccount) return null;
|
||||
|
||||
const { network } = selectedAccount;
|
||||
const {
|
||||
coinSymbol,
|
||||
selectedCurrency,
|
||||
account,
|
||||
network
|
||||
} = props.selectedAccount;
|
||||
|
||||
const {
|
||||
networkSymbol,
|
||||
currency,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
calculatingGasLimit,
|
||||
nonce,
|
||||
data,
|
||||
errors,
|
||||
@ -120,10 +122,11 @@ const AdvancedForm = (props: Props) => {
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={ gasLimit }
|
||||
disabled={ coinSymbol === selectedCurrency && data.length > 0 }
|
||||
disabled={ networkSymbol === currency && data.length > 0 }
|
||||
onChange={ event => onGasLimitChange(event.target.value) } />
|
||||
{ errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null }
|
||||
{ warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null }
|
||||
{ calculatingGasLimit ? (<span className="info">Calculating...</span>) : null }
|
||||
</div>
|
||||
<div className="column">
|
||||
<label>
|
||||
@ -157,7 +160,7 @@ const AdvancedForm = (props: Props) => {
|
||||
<span className="what-is-it"></span>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<textarea disabled={ coinSymbol !== selectedCurrency } value={ coinSymbol !== selectedCurrency ? '' : data } onChange={ event => onDataChange(event.target.value) }></textarea>
|
||||
<textarea disabled={ networkSymbol !== currency } value={ networkSymbol !== currency ? '' : data } onChange={ event => onDataChange(event.target.value) }></textarea>
|
||||
{ errors.data ? (<span className="error">{ errors.data }</span>) : null }
|
||||
</div>
|
||||
|
||||
|
@ -6,40 +6,52 @@ import Select from 'react-select';
|
||||
import AdvancedForm from './AdvancedForm';
|
||||
import PendingTransactions from './PendingTransactions';
|
||||
import { FeeSelectValue, FeeSelectOption } from './FeeSelect';
|
||||
import { Notification } from '~/js/components/common/Notification';
|
||||
import SelectedAccount from '../SelectedAccount';
|
||||
import { findAccountTokens } from '~/js/reducers/TokensReducer';
|
||||
import { calculate, validation } from '~/js/actions/SendFormActions';
|
||||
|
||||
import { findToken } from '~/js/reducers/TokensReducer';
|
||||
|
||||
import type { Props } from './index';
|
||||
import type { ComponentState } from '../SelectedAccount';
|
||||
import type { Token } from '~/flowtype';
|
||||
|
||||
|
||||
export default class SendContainer extends Component<Props> {
|
||||
|
||||
componentWillReceiveProps(newProps: Props) {
|
||||
calculate(this.props, newProps);
|
||||
validation(newProps);
|
||||
}
|
||||
|
||||
export default class Send extends SelectedAccount<Props> {
|
||||
render() {
|
||||
return super.render() || _render(this.props, this.state);
|
||||
return (
|
||||
<SelectedAccount { ...this.props }>
|
||||
<Send { ...this.props} />
|
||||
</SelectedAccount>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const _render = (props: Props, state: ComponentState): React$Element<string> => {
|
||||
|
||||
const Send = (props: Props) => {
|
||||
|
||||
const device = props.wallet.selectedDevice;
|
||||
const {
|
||||
device,
|
||||
account,
|
||||
network,
|
||||
discovery,
|
||||
deviceStatusNotification
|
||||
} = state;
|
||||
const selectedAccount = props.selectedAccount;
|
||||
tokens
|
||||
} = props.selectedAccount;
|
||||
|
||||
if (!device || !account || !discovery || !selectedAccount) return <section></section>;
|
||||
if (!device || !account || !discovery || !network) return null;
|
||||
|
||||
const tokens = findAccountTokens(props.tokens, account);
|
||||
const { network } = selectedAccount;
|
||||
|
||||
const {
|
||||
address,
|
||||
amount,
|
||||
setMax,
|
||||
coinSymbol,
|
||||
selectedCurrency,
|
||||
networkSymbol,
|
||||
currency,
|
||||
feeLevels,
|
||||
selectedFeeLevel,
|
||||
gasPriceNeedsUpdate,
|
||||
@ -48,6 +60,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
warnings,
|
||||
infos,
|
||||
advanced,
|
||||
data,
|
||||
sending,
|
||||
} = props.sendForm;
|
||||
|
||||
@ -61,13 +74,12 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
onSend,
|
||||
} = props.sendFormActions;
|
||||
|
||||
const selectedCoin = selectedAccount.coin;
|
||||
const fiatRate = props.fiat.find(f => f.network === network);
|
||||
|
||||
const tokensSelectData = tokens.map(t => {
|
||||
return { value: t.symbol, label: t.symbol };
|
||||
});
|
||||
tokensSelectData.unshift({ value: selectedCoin.symbol, label: selectedCoin.symbol });
|
||||
tokensSelectData.unshift({ value: network.symbol, label: network.symbol });
|
||||
|
||||
const setMaxClassName: string = setMax ? 'set-max enabled' : 'set-max';
|
||||
|
||||
@ -89,10 +101,10 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
|
||||
let buttonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending;
|
||||
let buttonLabel: string = 'Send';
|
||||
if (coinSymbol !== selectedCurrency && amount.length > 0 && !errors.amount) {
|
||||
buttonLabel += ` ${amount} ${ selectedCurrency.toUpperCase() }`
|
||||
} else if (coinSymbol === selectedCurrency && total !== '0') {
|
||||
buttonLabel += ` ${total} ${ selectedCoin.symbol }`;
|
||||
if (networkSymbol !== currency && amount.length > 0 && !errors.amount) {
|
||||
buttonLabel += ` ${amount} ${ currency.toUpperCase() }`
|
||||
} else if (networkSymbol === currency && total !== '0') {
|
||||
buttonLabel += ` ${total} ${ network.symbol }`;
|
||||
}
|
||||
|
||||
if (!device.connected){
|
||||
@ -106,13 +118,8 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
buttonDisabled = true;
|
||||
}
|
||||
|
||||
let notification = null;
|
||||
|
||||
return (
|
||||
<section className="send-form">
|
||||
|
||||
{ deviceStatusNotification }
|
||||
|
||||
<h2>Send Ethereum or tokens</h2>
|
||||
<div className="row address-input">
|
||||
<label>Address</label>
|
||||
@ -152,7 +159,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
searchable={ false }
|
||||
clearable= { false }
|
||||
multi={ false }
|
||||
value={ selectedCurrency }
|
||||
value={ currency }
|
||||
disabled={ tokensSelectData.length < 2 }
|
||||
onChange={ onCurrencyChange }
|
||||
options={ tokensSelectData } />
|
||||
@ -172,6 +179,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
onChange={ onFeeLevelChange }
|
||||
valueComponent={ FeeSelectValue }
|
||||
optionComponent={ FeeSelectOption }
|
||||
disabled={ networkSymbol === currency && data.length > 0 }
|
||||
optionClassName="fee-option"
|
||||
options={ feeLevels } />
|
||||
</div>
|
||||
@ -187,7 +195,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
pending={ props.pending }
|
||||
tokens={ props.tokens }
|
||||
account={ account }
|
||||
selectedCoin={ selectedCoin } />
|
||||
selectedCoin={ network } />
|
||||
|
||||
</section>
|
||||
);
|
||||
|
@ -6,7 +6,6 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { default as SendFormActions } from '~/js/actions/SendFormActions';
|
||||
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
|
||||
import SendForm from './SendForm';
|
||||
|
||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||
@ -28,14 +27,14 @@ export type DispatchProps = BaseDispatchProps & {
|
||||
sendFormActions: typeof SendFormActions
|
||||
}
|
||||
|
||||
export type Props = StateProps & DispatchProps;
|
||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
||||
|
||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => {
|
||||
return {
|
||||
className: "send-from",
|
||||
selectedAccount: state.selectedAccount,
|
||||
devices: state.devices,
|
||||
accounts: state.accounts,
|
||||
discovery: state.discovery,
|
||||
wallet: state.wallet,
|
||||
|
||||
tokens: state.tokens,
|
||||
pending: state.pending,
|
||||
sendForm: state.sendForm,
|
||||
@ -46,10 +45,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
||||
|
||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => {
|
||||
return {
|
||||
selectedAccountActions: bindActionCreators(SelectedAccountActions, dispatch),
|
||||
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
||||
initAccount: bindActionCreators(SendFormActions.init, dispatch),
|
||||
disposeAccount: bindActionCreators(SendFormActions.dispose, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { Async } from 'react-select';
|
||||
import { Async as AsyncSelect } from 'react-select';
|
||||
import Tooltip from 'rc-tooltip';
|
||||
|
||||
import { resolveAfter } from '~/js/utils/promiseUtils';
|
||||
@ -13,56 +13,38 @@ import SummaryDetails from './SummaryDetails.js';
|
||||
import SummaryTokens from './SummaryTokens.js';
|
||||
|
||||
import type { Props } from './index';
|
||||
import type { ComponentState } from '../SelectedAccount';
|
||||
|
||||
import type { TrezorDevice } from '~/flowtype';
|
||||
import type { NetworkToken } from '~/js/reducers/LocalStorageReducer';
|
||||
import type { Account } from '~/js/reducers/AccountsReducer';
|
||||
import type { Discovery } from '~/js/reducers/DiscoveryReducer';
|
||||
import { findAccountTokens } from '~/js/reducers/TokensReducer';
|
||||
|
||||
export default class Summary extends SelectedAccount<Props> {
|
||||
render() {
|
||||
return super.render() || _render(this.props, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
const _render = (props: Props, state: ComponentState): React$Element<string> => {
|
||||
|
||||
const Summary = (props: Props) => {
|
||||
const device = props.wallet.selectedDevice;
|
||||
const {
|
||||
device,
|
||||
account,
|
||||
deviceStatusNotification
|
||||
} = state;
|
||||
const selectedAccount = props.selectedAccount;
|
||||
network,
|
||||
tokens,
|
||||
} = props.selectedAccount;
|
||||
|
||||
if (!device || !account || !selectedAccount) return <section></section>;
|
||||
|
||||
|
||||
const tokens = findAccountTokens(props.tokens, account);
|
||||
const explorerLink: string = `${selectedAccount.coin.explorer.address}${account.address}`;
|
||||
// flow
|
||||
if (!device || !account || !network) return null;
|
||||
|
||||
const tokensTooltip = (
|
||||
<div className="tooltip-wrapper">
|
||||
Insert token name, symbol or address to be able to send it.
|
||||
</div>
|
||||
);
|
||||
const explorerLink: string = `${network.explorer.address}${account.address}`;
|
||||
|
||||
return (
|
||||
|
||||
<section className="summary">
|
||||
{ deviceStatusNotification }
|
||||
|
||||
<h2 className={ `summary-header ${selectedAccount.network}` }>
|
||||
Account #{ parseInt(selectedAccount.index) + 1 }
|
||||
<div>
|
||||
<h2 className={ `summary-header ${account.network}` }>
|
||||
Account #{ parseInt(account.index) + 1 }
|
||||
<a href={ explorerLink } className="gray" target="_blank" rel="noreferrer noopener">See full transaction history</a>
|
||||
</h2>
|
||||
|
||||
<SummaryDetails
|
||||
coin={ selectedAccount.coin }
|
||||
coin={ network }
|
||||
summary={ props.summary }
|
||||
balance={ account.balance }
|
||||
network={ selectedAccount.network }
|
||||
network={ network.network }
|
||||
fiat={ props.fiat }
|
||||
localStorage={ props.localStorage }
|
||||
onToggle={ props.onDetailsToggle } />
|
||||
@ -79,7 +61,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 Lahod */}
|
||||
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 T01 */}
|
||||
<div className="filter">
|
||||
<Async
|
||||
<AsyncSelect
|
||||
className="token-select"
|
||||
multi={ false }
|
||||
autoload={ false }
|
||||
@ -102,25 +84,27 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
|
||||
return o;
|
||||
}
|
||||
});
|
||||
|
||||
// return options.filter(o => {
|
||||
// return !tokens.find(t => t.symbol === o.symbol);
|
||||
// });
|
||||
}
|
||||
}
|
||||
valueKey="symbol"
|
||||
labelKey="name"
|
||||
placeholder="Search for token"
|
||||
searchPromptText="Type token name or address"
|
||||
noResultsText="Token not found"
|
||||
|
||||
/>
|
||||
noResultsText="Token not found" />
|
||||
|
||||
</div>
|
||||
|
||||
<SummaryTokens tokens={ tokens } removeToken={ props.removeToken } />
|
||||
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default (props: Props) => {
|
||||
return (
|
||||
<SelectedAccount { ...props }>
|
||||
<Summary { ...props} />
|
||||
</SelectedAccount>
|
||||
);
|
||||
}
|
@ -6,7 +6,6 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Summary from './Summary';
|
||||
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
|
||||
import * as SummaryActions from '~/js/actions/SummaryActions';
|
||||
import * as TokenActions from '~/js/actions/TokenActions';
|
||||
|
||||
@ -21,7 +20,7 @@ type StateProps = BaseStateProps & {
|
||||
summary: $ElementType<State, 'summary'>,
|
||||
fiat: $ElementType<State, 'fiat'>,
|
||||
localStorage: $ElementType<State, 'localStorage'>,
|
||||
}
|
||||
};
|
||||
|
||||
type DispatchProps = BaseDispatchProps & {
|
||||
onDetailsToggle: typeof SummaryActions.onDetailsToggle,
|
||||
@ -30,14 +29,13 @@ type DispatchProps = BaseDispatchProps & {
|
||||
removeToken: typeof TokenActions.remove,
|
||||
}
|
||||
|
||||
export type Props = StateProps & DispatchProps;
|
||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
||||
|
||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => {
|
||||
return {
|
||||
className: "summary",
|
||||
selectedAccount: state.selectedAccount,
|
||||
devices: state.devices,
|
||||
accounts: state.accounts,
|
||||
discovery: state.discovery,
|
||||
wallet: state.wallet,
|
||||
|
||||
tokens: state.tokens,
|
||||
summary: state.summary,
|
||||
@ -48,11 +46,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
||||
|
||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => {
|
||||
return {
|
||||
selectedAccountActions: bindActionCreators(SelectedAccountActions, dispatch),
|
||||
|
||||
initAccount: bindActionCreators(SummaryActions.init, dispatch),
|
||||
disposeAccount: bindActionCreators(SummaryActions.dispose, dispatch),
|
||||
|
||||
onDetailsToggle: bindActionCreators(SummaryActions.onDetailsToggle, dispatch),
|
||||
addToken: bindActionCreators(TokenActions.add, dispatch),
|
||||
loadTokens: bindActionCreators(TokenActions.load, dispatch),
|
||||
|
@ -298,13 +298,3 @@ export const getNewInstance = (devices: State, device: Device | TrezorDevice): n
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
export const findDevice = (devices: State, deviceId: string, deviceState: string, instance: ?number): ?TrezorDevice => {
|
||||
return devices.find(d => {
|
||||
// TODO: && (instance && d.instance === instance)
|
||||
if (d.features && d.features.device_id === deviceId && d.state === deviceState) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
import { UI } from 'trezor-connect';
|
||||
import * as RECEIVE from '../actions/constants/receive';
|
||||
import * as ACCOUNT from '../actions/constants/account';
|
||||
import type { Action } from '~/flowtype';
|
||||
|
||||
|
||||
@ -23,7 +24,7 @@ export default (state: State = initialState, action: Action): State => {
|
||||
case RECEIVE.INIT :
|
||||
return action.state;
|
||||
|
||||
case RECEIVE.DISPOSE :
|
||||
case ACCOUNT.DISPOSE :
|
||||
return initialState;
|
||||
|
||||
case RECEIVE.SHOW_ADDRESS :
|
||||
|
@ -2,32 +2,41 @@
|
||||
'use strict';
|
||||
|
||||
import * as ACCOUNT from '../actions/constants/account';
|
||||
import * as CONNECT from '../actions/constants/TrezorConnect';
|
||||
|
||||
import type { Action } from '~/flowtype';
|
||||
import type { Coin } from './LocalStorageReducer';
|
||||
import type {
|
||||
Action,
|
||||
Account,
|
||||
Coin,
|
||||
Token,
|
||||
Discovery,
|
||||
Web3Instance
|
||||
} from '~/flowtype';
|
||||
|
||||
export type State = {
|
||||
+index: number;
|
||||
+deviceState: string;
|
||||
+deviceId: string;
|
||||
+deviceInstance: ?number;
|
||||
+network: string;
|
||||
+coin: Coin;
|
||||
+location: string;
|
||||
location?: string;
|
||||
|
||||
account: ?Account;
|
||||
network: ?Coin;
|
||||
tokens: Array<Token>,
|
||||
web3: ?Web3Instance,
|
||||
discovery: ?Discovery
|
||||
};
|
||||
|
||||
export const initialState: ?State = null;
|
||||
export const initialState: State = {
|
||||
location: '/',
|
||||
account: null,
|
||||
network: null,
|
||||
tokens: [],
|
||||
web3: null,
|
||||
discovery: null
|
||||
};
|
||||
|
||||
export default (state: ?State = initialState, action: Action): ?State => {
|
||||
export default (state: State = initialState, action: Action): State => {
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case ACCOUNT.INIT :
|
||||
return action.state;
|
||||
|
||||
case ACCOUNT.DISPOSE :
|
||||
return initialState;
|
||||
case ACCOUNT.UPDATE_SELECTED_ACCOUNT :
|
||||
return action.payload;
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
@ -4,6 +4,8 @@
|
||||
import * as SEND from '../actions/constants/send';
|
||||
import * as WEB3 from '../actions/constants/web3';
|
||||
import * as ACCOUNT from '../actions/constants/account';
|
||||
import * as WALLET from '../actions/constants/wallet';
|
||||
|
||||
import EthereumjsUnits from 'ethereumjs-units';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { getFeeLevels } from '../actions/SendFormActions';
|
||||
@ -14,10 +16,9 @@ import type {
|
||||
} from '../actions/Web3Actions';
|
||||
|
||||
export type State = {
|
||||
+network: string;
|
||||
+coinSymbol: string;
|
||||
selectedCurrency: string;
|
||||
balanceNeedUpdate: boolean;
|
||||
+networkName: string;
|
||||
+networkSymbol: string;
|
||||
currency: string;
|
||||
|
||||
// form fields
|
||||
advanced: boolean;
|
||||
@ -31,14 +32,17 @@ export type State = {
|
||||
recommendedGasPrice: string;
|
||||
gasPriceNeedsUpdate: boolean;
|
||||
gasLimit: string;
|
||||
calculatingGasLimit: boolean;
|
||||
gasPrice: string;
|
||||
data: string;
|
||||
nonce: string;
|
||||
total: string;
|
||||
sending: boolean;
|
||||
|
||||
errors: {[k: string]: string};
|
||||
warnings: {[k: string]: string};
|
||||
infos: {[k: string]: string};
|
||||
|
||||
sending: boolean;
|
||||
}
|
||||
|
||||
export type FeeLevel = {
|
||||
@ -49,14 +53,13 @@ export type FeeLevel = {
|
||||
|
||||
|
||||
export const initialState: State = {
|
||||
network: '',
|
||||
coinSymbol: '',
|
||||
selectedCurrency: '',
|
||||
networkName: '',
|
||||
networkSymbol: '',
|
||||
currency: '',
|
||||
|
||||
advanced: false,
|
||||
untouched: true,
|
||||
touched: {},
|
||||
balanceNeedUpdate: false,
|
||||
address: '',
|
||||
//address: '0x574BbB36871bA6b78E27f4B4dCFb76eA0091880B',
|
||||
amount: '',
|
||||
@ -70,6 +73,7 @@ export const initialState: State = {
|
||||
recommendedGasPrice: '0',
|
||||
gasPriceNeedsUpdate: false,
|
||||
gasLimit: '0',
|
||||
calculatingGasLimit: false,
|
||||
gasPrice: '0',
|
||||
data: '',
|
||||
nonce: '0',
|
||||
@ -88,13 +92,13 @@ const onGasPriceUpdated = (state: State, action: Web3UpdateGasPriceAction): Stat
|
||||
// }
|
||||
// const newPrice = getRandomInt(10, 50).toString();
|
||||
const newPrice: string = EthereumjsUnits.convert(action.gasPrice, 'wei', 'gwei');
|
||||
if (action.network === state.network && newPrice !== state.recommendedGasPrice) {
|
||||
if (action.network === state.networkName && newPrice !== state.recommendedGasPrice) {
|
||||
const newState: State = { ...state };
|
||||
if (!state.untouched) {
|
||||
newState.gasPriceNeedsUpdate = true;
|
||||
newState.recommendedGasPrice = newPrice;
|
||||
} else {
|
||||
const newFeeLevels = getFeeLevels(state.coinSymbol, newPrice, state.gasLimit);
|
||||
const newFeeLevels = getFeeLevels(state.networkSymbol, newPrice, state.gasLimit);
|
||||
const selectedFeeLevel: ?FeeLevel = newFeeLevels.find(f => f.value === 'Normal');
|
||||
if (!selectedFeeLevel) return state;
|
||||
newState.recommendedGasPrice = newPrice;
|
||||
@ -107,19 +111,6 @@ const onGasPriceUpdated = (state: State, action: Web3UpdateGasPriceAction): Stat
|
||||
return state;
|
||||
}
|
||||
|
||||
const onBalanceUpdated = (state: State, action: any): State => {
|
||||
// balanceNeedUpdate
|
||||
// if (state.senderAddress === action.address) {
|
||||
// return {
|
||||
// ...state,
|
||||
// balance: '1'
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: handle balance update during send form life cycle
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
export default (state: State = initialState, action: Action): State => {
|
||||
|
||||
@ -128,7 +119,7 @@ export default (state: State = initialState, action: Action): State => {
|
||||
case SEND.INIT :
|
||||
return action.state;
|
||||
|
||||
case SEND.DISPOSE :
|
||||
case ACCOUNT.DISPOSE :
|
||||
return initialState;
|
||||
|
||||
// this will be called right after Web3 instance initialization before any view is shown
|
||||
@ -136,8 +127,6 @@ export default (state: State = initialState, action: Action): State => {
|
||||
case WEB3.GAS_PRICE_UPDATED :
|
||||
return onGasPriceUpdated(state, action);
|
||||
|
||||
case ACCOUNT.SET_BALANCE :
|
||||
return onBalanceUpdated(state, action);
|
||||
|
||||
case SEND.TOGGLE_ADVANCED :
|
||||
return {
|
||||
@ -148,6 +137,7 @@ export default (state: State = initialState, action: Action): State => {
|
||||
|
||||
// user actions
|
||||
case SEND.ADDRESS_CHANGE :
|
||||
case SEND.ADDRESS_VALIDATION :
|
||||
case SEND.AMOUNT_CHANGE :
|
||||
case SEND.SET_MAX :
|
||||
case SEND.CURRENCY_CHANGE :
|
||||
@ -165,33 +155,12 @@ export default (state: State = initialState, action: Action): State => {
|
||||
sending: true,
|
||||
}
|
||||
|
||||
case SEND.TX_COMPLETE :
|
||||
return {
|
||||
...state,
|
||||
|
||||
sending: false,
|
||||
untouched: true,
|
||||
touched: {},
|
||||
address: '',
|
||||
amount: '',
|
||||
setMax: false,
|
||||
gasPriceNeedsUpdate: false,
|
||||
gasLimit: state.gasLimit,
|
||||
gasPrice: state.recommendedGasPrice,
|
||||
data: '',
|
||||
nonce: new BigNumber(state.nonce).plus(1).toString(),
|
||||
total: '0',
|
||||
errors: {},
|
||||
warnings: {},
|
||||
infos: {},
|
||||
}
|
||||
case SEND.TX_ERROR :
|
||||
return {
|
||||
...state,
|
||||
sending: false,
|
||||
}
|
||||
|
||||
|
||||
case SEND.VALIDATION :
|
||||
return {
|
||||
...state,
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import * as ACCOUNT from '../actions/constants/account';
|
||||
import * as SUMMARY from '../actions/constants/summary';
|
||||
import type { Action } from '~/flowtype';
|
||||
import type { NetworkToken } from './LocalStorageReducer';
|
||||
@ -13,13 +14,15 @@ export type State = {
|
||||
export const initialState: State = {
|
||||
details: true,
|
||||
selectedToken: null
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default (state: State = initialState, action: Action): State => {
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case ACCOUNT.DISPOSE :
|
||||
return initialState;
|
||||
|
||||
case SUMMARY.INIT :
|
||||
return action.state;
|
||||
|
||||
|
@ -40,8 +40,20 @@ export const getSelectedDevice = (state: State): ?TrezorDevice => {
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
export const isSelectedDevice = (current: ?TrezorDevice, device: ?TrezorDevice): boolean => {
|
||||
return (current && device && (current.path === device.path || current.instance === device.instance)) ? true : false;
|
||||
return (current && device && (current.path === device.path && current.instance === device.instance)) ? true : false;
|
||||
}
|
||||
|
||||
// find device by id and state
|
||||
export const findDevice = (devices: Array<TrezorDevice>, deviceId: string, deviceState: string, instance: ?number): ?TrezorDevice => {
|
||||
return devices.find(d => {
|
||||
// TODO: && (instance && d.instance === instance)
|
||||
if (d.features && d.features.device_id === deviceId && d.state === deviceState) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
export const getSelectedAccount = (state: State): ?Account => {
|
||||
@ -71,10 +83,10 @@ export const getDiscoveryProcess = (state: State): ?Discovery => {
|
||||
return state.discovery.find(d => d.deviceState === device.state && d.network === locationState.network);
|
||||
}
|
||||
|
||||
export const getTokens = (state: State): Array<Token> => {
|
||||
const account = state.selectedAccount.account;
|
||||
if (!account) return state.selectedAccount.tokens;
|
||||
return state.tokens.filter(t => t.ethAddress === account.address && t.network === account.network && t.deviceState === account.deviceState);
|
||||
export const getTokens = (state: State, account: ?Account): Array<Token> => {
|
||||
const a = account;
|
||||
if (!a) return state.selectedAccount.tokens;
|
||||
return state.tokens.filter(t => t.ethAddress === a.address && t.network === a.network && t.deviceState === a.deviceState);
|
||||
}
|
||||
|
||||
export const getWeb3 = (state: State): ?Web3Instance => {
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import * as WALLET from '../actions/constants/wallet';
|
||||
import * as SEND from '../actions/constants/wallet';
|
||||
|
||||
import * as WalletActions from '../actions/WalletActions';
|
||||
import * as LocalStorageActions from '../actions/LocalStorageActions';
|
||||
import * as TrezorConnectActions from '../actions/TrezorConnectActions';
|
||||
import * as SelectedAccountActions from '../actions/SelectedAccountActions';
|
||||
|
||||
import type {
|
||||
Middleware,
|
||||
@ -19,24 +21,6 @@ import type {
|
||||
TrezorDevice,
|
||||
} from '~/flowtype';
|
||||
|
||||
|
||||
const getSelectedDevice = (state: State): ?TrezorDevice => {
|
||||
const locationState = state.router.location.state;
|
||||
if (!locationState.device) return undefined;
|
||||
|
||||
const instance: ?number = locationState.deviceInstance ? parseInt(locationState.deviceInstance) : undefined;
|
||||
return state.devices.find(d => {
|
||||
if (d.unacquired && d.path === locationState.device) {
|
||||
return true;
|
||||
} else if (d.features && d.features.bootloader_mode && d.path === locationState.device) {
|
||||
return true;
|
||||
} else if (d.features && d.features.device_id === locationState.device && d.instance === instance) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware
|
||||
*/
|
||||
@ -58,38 +42,22 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
||||
// pass action
|
||||
next(action);
|
||||
|
||||
const state = api.getState();
|
||||
// update common values in WallerReducer
|
||||
api.dispatch( WalletActions.updateSelectedValues(prevState, action) );
|
||||
|
||||
// handle devices state change
|
||||
if (locationChange || prevState.devices !== state.devices) {
|
||||
const device = getSelectedDevice(state);
|
||||
const currentDevice = state.wallet.selectedDevice;
|
||||
// update common values in SelectedAccountReducer
|
||||
api.dispatch( SelectedAccountActions.updateSelectedValues(prevState, action) );
|
||||
|
||||
if (state.wallet.selectedDevice !== device) {
|
||||
if (!locationChange && currentDevice && device && (currentDevice.path === device.path || currentDevice.instance === device.instance)) {
|
||||
api.dispatch({
|
||||
type: WALLET.UPDATE_SELECTED_DEVICE,
|
||||
device
|
||||
})
|
||||
} else {
|
||||
api.dispatch({
|
||||
type: WALLET.SET_SELECTED_DEVICE,
|
||||
device
|
||||
});
|
||||
|
||||
if (device) {
|
||||
// selected device changed
|
||||
if (action.type === WALLET.SET_SELECTED_DEVICE) {
|
||||
if (action.device) {
|
||||
api.dispatch( TrezorConnectActions.getSelectedDeviceState() );
|
||||
} else {
|
||||
api.dispatch( TrezorConnectActions.switchToFirstAvailableDevice() );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default WalletService;
|
Loading…
Reference in New Issue
Block a user