2018-09-22 16:49:05 +00:00
|
|
|
/* @flow */
|
2019-01-11 16:48:32 +00:00
|
|
|
import TrezorConnect from 'trezor-connect';
|
2018-09-22 16:49:05 +00:00
|
|
|
import BigNumber from 'bignumber.js';
|
2018-12-05 13:12:23 +00:00
|
|
|
import * as SEND from 'actions/constants/send';
|
2018-09-22 16:49:05 +00:00
|
|
|
import { findDevice, getPendingAmount } from 'reducers/utils';
|
2018-12-03 20:01:33 +00:00
|
|
|
import { toDecimalAmount } from 'utils/formatUtils';
|
2019-03-12 15:03:14 +00:00
|
|
|
import { toFiatCurrency } from 'utils/fiatConverter';
|
2019-04-22 09:31:55 +00:00
|
|
|
import * as validators from 'utils/validators';
|
2019-04-29 13:36:05 +00:00
|
|
|
import l10nMessages from 'views/Wallet/views/Account/Send/validation.messages';
|
|
|
|
import l10nCommonMessages from 'views/common.messages';
|
2018-09-22 16:49:05 +00:00
|
|
|
|
|
|
|
import type {
|
|
|
|
Dispatch,
|
|
|
|
GetState,
|
|
|
|
PayloadAction,
|
2019-01-11 16:48:32 +00:00
|
|
|
PromiseAction,
|
2018-12-28 15:15:18 +00:00
|
|
|
BlockchainFeeLevel,
|
2018-09-22 16:49:05 +00:00
|
|
|
} from 'flowtype';
|
2018-11-29 20:02:56 +00:00
|
|
|
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
2018-09-22 16:49:05 +00:00
|
|
|
|
2018-12-20 18:30:00 +00:00
|
|
|
import AddressValidator from 'wallet-address-validator';
|
2019-04-22 09:31:55 +00:00
|
|
|
|
2018-09-22 16:49:05 +00:00
|
|
|
// general regular expressions
|
2019-04-16 14:05:13 +00:00
|
|
|
const U_INT_32 = 0xffffffff;
|
2018-09-22 16:49:05 +00:00
|
|
|
|
2018-12-05 13:12:23 +00:00
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Called from SendFormActions.observe
|
|
|
|
* Reaction for BLOCKCHAIN.FEE_UPDATED action
|
|
|
|
*/
|
|
|
|
export const onFeeUpdated = (
|
|
|
|
network: string,
|
|
|
|
feeLevels: Array<BlockchainFeeLevel>
|
|
|
|
): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
|
2018-12-05 13:12:23 +00:00
|
|
|
const state = getState().sendFormRipple;
|
|
|
|
if (network === state.networkSymbol) return;
|
|
|
|
|
|
|
|
if (!state.untouched) {
|
|
|
|
// if there is a transaction draft let the user know
|
|
|
|
// and let him update manually
|
|
|
|
dispatch({
|
|
|
|
type: SEND.CHANGE,
|
|
|
|
networkType: 'ripple',
|
|
|
|
state: {
|
|
|
|
...state,
|
|
|
|
feeNeedsUpdate: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-12-28 15:15:18 +00:00
|
|
|
// automatically update feeLevels
|
|
|
|
const newFeeLevels = dispatch(getFeeLevels(feeLevels));
|
|
|
|
const selectedFeeLevel = getSelectedFeeLevel(newFeeLevels, state.selectedFeeLevel);
|
2018-12-05 13:12:23 +00:00
|
|
|
dispatch({
|
|
|
|
type: SEND.CHANGE,
|
|
|
|
networkType: 'ripple',
|
|
|
|
state: {
|
|
|
|
...state,
|
|
|
|
feeNeedsUpdate: false,
|
2018-12-28 15:15:18 +00:00
|
|
|
feeLevels: newFeeLevels,
|
2018-12-05 13:12:23 +00:00
|
|
|
selectedFeeLevel,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-09-22 16:49:05 +00:00
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Recalculate amount, total and fees
|
|
|
|
*/
|
|
|
|
export const validation = (prevState: State): PayloadAction<State> => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): State => {
|
2018-09-22 16:49:05 +00:00
|
|
|
// clone deep nested object
|
|
|
|
// to avoid overrides across state history
|
2018-11-29 20:02:56 +00:00
|
|
|
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
|
2018-09-22 16:49:05 +00:00
|
|
|
// reset errors
|
|
|
|
state.errors = {};
|
|
|
|
state.warnings = {};
|
|
|
|
state.infos = {};
|
2018-12-28 15:15:18 +00:00
|
|
|
state = dispatch(updateCustomFeeLabel(state));
|
2019-01-02 10:34:36 +00:00
|
|
|
state = dispatch(recalculateTotalAmount(state));
|
2018-09-22 16:49:05 +00:00
|
|
|
state = dispatch(addressValidation(state));
|
|
|
|
state = dispatch(addressLabel(state));
|
|
|
|
state = dispatch(amountValidation(state));
|
2018-12-28 15:15:18 +00:00
|
|
|
state = dispatch(feeValidation(state));
|
2019-01-09 13:31:40 +00:00
|
|
|
state = dispatch(destinationTagValidation(state));
|
2019-01-11 16:48:32 +00:00
|
|
|
if (state.touched.address && prevState.address !== state.address) {
|
|
|
|
dispatch(addressBalanceValidation(state));
|
|
|
|
}
|
2018-09-22 16:49:05 +00:00
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): State => {
|
|
|
|
const { account, network, pending } = getState().selectedAccount;
|
2019-01-12 18:31:42 +00:00
|
|
|
if (!account || account.networkType !== 'ripple' || !network) return $state;
|
2018-09-22 16:49:05 +00:00
|
|
|
|
|
|
|
const state = { ...$state };
|
2019-01-02 10:34:36 +00:00
|
|
|
const fee = toDecimalAmount(state.selectedFeeLevel.fee, network.decimals);
|
2018-09-22 16:49:05 +00:00
|
|
|
|
|
|
|
if (state.setMax) {
|
2018-11-29 20:02:56 +00:00
|
|
|
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
2019-03-04 12:33:02 +00:00
|
|
|
const availableBalance = new BigNumber(account.balance)
|
|
|
|
.minus(account.reserve)
|
|
|
|
.minus(pendingAmount);
|
2019-01-02 10:34:36 +00:00
|
|
|
state.amount = calculateMaxAmount(availableBalance, fee);
|
2019-03-12 15:03:14 +00:00
|
|
|
|
|
|
|
// calculate amount in local currency
|
|
|
|
const { localCurrency } = getState().sendFormRipple;
|
|
|
|
const fiatRates = getState().fiat.find(f => f.network === state.networkName);
|
|
|
|
const localAmount = toFiatCurrency(state.amount, localCurrency, fiatRates);
|
|
|
|
state.localAmount = localAmount;
|
2018-09-22 16:49:05 +00:00
|
|
|
}
|
|
|
|
|
2019-01-02 10:34:36 +00:00
|
|
|
state.total = calculateTotal(state.amount, fee);
|
2018-12-28 15:15:18 +00:00
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const updateCustomFeeLabel = ($state: State): PayloadAction<State> => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): State => {
|
2019-01-02 10:34:36 +00:00
|
|
|
const { network } = getState().selectedAccount;
|
|
|
|
if (!network) return $state; // flowtype fallback
|
|
|
|
|
2018-12-28 15:15:18 +00:00
|
|
|
const state = { ...$state };
|
|
|
|
if ($state.selectedFeeLevel.value === 'Custom') {
|
|
|
|
state.selectedFeeLevel = {
|
|
|
|
...state.selectedFeeLevel,
|
|
|
|
fee: state.fee,
|
2019-01-02 10:34:36 +00:00
|
|
|
label: `${toDecimalAmount(state.fee, network.decimals)} ${state.networkSymbol}`,
|
2018-12-28 15:15:18 +00:00
|
|
|
};
|
|
|
|
}
|
2018-09-22 16:49:05 +00:00
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Address value validation
|
|
|
|
*/
|
|
|
|
const addressValidation = ($state: State): PayloadAction<State> => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): State => {
|
2018-09-22 16:49:05 +00:00
|
|
|
const state = { ...$state };
|
|
|
|
if (!state.touched.address) return state;
|
|
|
|
|
2018-12-03 20:01:33 +00:00
|
|
|
const { account, network } = getState().selectedAccount;
|
|
|
|
if (!account || !network) return state;
|
2018-11-29 20:02:56 +00:00
|
|
|
|
2018-09-22 16:49:05 +00:00
|
|
|
const { address } = state;
|
|
|
|
|
|
|
|
if (address.length < 1) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.address = l10nMessages.TR_ADDRESS_IS_NOT_SET;
|
2018-12-20 18:30:00 +00:00
|
|
|
} else if (!AddressValidator.validate(address, 'XRP')) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.address = l10nMessages.TR_ADDRESS_IS_NOT_VALID;
|
2018-12-21 09:58:53 +00:00
|
|
|
} else if (address.toLowerCase() === account.descriptor.toLowerCase()) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.address = l10nMessages.TR_CANNOT_SEND_TO_MYSELF;
|
2018-09-22 16:49:05 +00:00
|
|
|
}
|
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
2019-01-11 16:48:32 +00:00
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Address balance validation
|
|
|
|
* Fetch data from trezor-connect and set minimum required amount in reducer
|
|
|
|
*/
|
|
|
|
const addressBalanceValidation = ($state: State): PromiseAction<void> => async (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): Promise<void> => {
|
2019-01-11 16:48:32 +00:00
|
|
|
const { network } = getState().selectedAccount;
|
|
|
|
if (!network) return;
|
|
|
|
|
|
|
|
let minAmount: string = '0';
|
2020-03-30 18:46:07 +00:00
|
|
|
const response = await TrezorConnect.getAccountInfo({
|
|
|
|
descriptor: $state.address,
|
2019-01-11 16:48:32 +00:00
|
|
|
coin: network.shortcut,
|
|
|
|
});
|
|
|
|
if (response.success) {
|
2020-03-30 18:46:07 +00:00
|
|
|
if (response.payload.empty) {
|
|
|
|
const reserve = response.payload.misc ? response.payload.misc.reserve : '0';
|
|
|
|
minAmount = toDecimalAmount(reserve || '0', network.decimals);
|
2019-01-11 16:48:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: consider checking local (known) accounts reserve instead of async fetching
|
|
|
|
|
|
|
|
// a2 (not empty): rJX2KwzaLJDyFhhtXKi3htaLfaUH2tptEX
|
|
|
|
// a4 (empty): r9skfe7kZkvqss7oMB3tuj4a59PXD5wRa2
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: SEND.CHANGE,
|
|
|
|
networkType: 'ripple',
|
|
|
|
state: {
|
|
|
|
...getState().sendFormRipple,
|
|
|
|
minAmount,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2018-09-22 16:49:05 +00:00
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Address label assignation
|
|
|
|
*/
|
|
|
|
const addressLabel = ($state: State): PayloadAction<State> => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): State => {
|
2018-09-22 16:49:05 +00:00
|
|
|
const state = { ...$state };
|
|
|
|
if (!state.touched.address || state.errors.address) return state;
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const { account, network } = getState().selectedAccount;
|
2018-09-22 16:49:05 +00:00
|
|
|
if (!account || !network) return state;
|
|
|
|
const { address } = state;
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const savedAccounts = getState().accounts.filter(
|
|
|
|
a => a.descriptor.toLowerCase() === address.toLowerCase()
|
|
|
|
);
|
2018-09-22 16:49:05 +00:00
|
|
|
if (savedAccounts.length > 0) {
|
|
|
|
// check if found account belongs to this network
|
2018-10-15 13:44:10 +00:00
|
|
|
const currentNetworkAccount = savedAccounts.find(a => a.network === network.shortcut);
|
2018-09-22 16:49:05 +00:00
|
|
|
if (currentNetworkAccount) {
|
2019-03-04 12:33:02 +00:00
|
|
|
const device = findDevice(
|
|
|
|
getState().devices,
|
|
|
|
currentNetworkAccount.deviceID,
|
|
|
|
currentNetworkAccount.deviceState
|
|
|
|
);
|
2018-09-22 16:49:05 +00:00
|
|
|
if (device) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.infos.address = {
|
|
|
|
...l10nCommonMessages.TR_DEVICE_LABEL_ACCOUNT_HASH,
|
|
|
|
values: {
|
|
|
|
deviceLabel: device.instanceLabel,
|
|
|
|
number: currentNetworkAccount.index + 1,
|
|
|
|
},
|
|
|
|
};
|
2018-09-22 16:49:05 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// corner-case: the same derivation path is used on different networks
|
|
|
|
const otherNetworkAccount = savedAccounts[0];
|
2019-03-04 12:33:02 +00:00
|
|
|
const device = findDevice(
|
|
|
|
getState().devices,
|
|
|
|
otherNetworkAccount.deviceID,
|
|
|
|
otherNetworkAccount.deviceState
|
|
|
|
);
|
2018-10-15 13:44:10 +00:00
|
|
|
const { networks } = getState().localStorage.config;
|
|
|
|
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
|
2018-09-22 16:49:05 +00:00
|
|
|
if (device && otherNetwork) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.warnings.address = {
|
|
|
|
...l10nCommonMessages.TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL,
|
|
|
|
values: {
|
|
|
|
deviceLabel: device.instanceLabel,
|
|
|
|
number: otherNetworkAccount.index + 1,
|
|
|
|
network: otherNetwork.name,
|
|
|
|
},
|
|
|
|
};
|
2018-09-22 16:49:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Amount value validation
|
|
|
|
*/
|
|
|
|
const amountValidation = ($state: State): PayloadAction<State> => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): State => {
|
2018-09-22 16:49:05 +00:00
|
|
|
const state = { ...$state };
|
|
|
|
if (!state.touched.amount) return state;
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const { account, pending } = getState().selectedAccount;
|
2019-01-11 16:55:36 +00:00
|
|
|
if (!account || account.networkType !== 'ripple') return state;
|
2018-09-22 16:49:05 +00:00
|
|
|
|
|
|
|
const { amount } = state;
|
|
|
|
if (amount.length < 1) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.amount = l10nMessages.TR_AMOUNT_IS_NOT_SET;
|
2019-04-22 11:43:44 +00:00
|
|
|
} else if (amount.length > 0 && !validators.isNumber(amount)) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.amount = l10nMessages.TR_AMOUNT_IS_NOT_A_NUMBER;
|
2018-09-22 16:49:05 +00:00
|
|
|
} else {
|
2018-12-03 20:01:33 +00:00
|
|
|
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
|
2019-04-22 11:43:44 +00:00
|
|
|
if (!validators.hasDecimals(state.amount, 6)) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.amount = {
|
|
|
|
...l10nMessages.TR_MAXIMUM_DECIMALS_ALLOWED,
|
|
|
|
values: { decimals: 6 },
|
|
|
|
};
|
2019-03-04 12:33:02 +00:00
|
|
|
} else if (
|
|
|
|
new BigNumber(state.total).isGreaterThan(
|
|
|
|
new BigNumber(account.balance).minus(pendingAmount)
|
|
|
|
)
|
|
|
|
) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.amount = l10nMessages.TR_NOT_ENOUGH_FUNDS;
|
2018-09-22 16:49:05 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-11 16:48:32 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
if (
|
|
|
|
!state.errors.amount &&
|
|
|
|
new BigNumber(account.balance).minus(state.total).lt(account.reserve)
|
|
|
|
) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.amount = {
|
|
|
|
...l10nMessages.TR_NOT_ENOUGH_FUNDS_RESERVED_AMOUNT,
|
|
|
|
values: {
|
|
|
|
reservedAmount: account.reserve,
|
|
|
|
networkSymbol: state.networkSymbol,
|
|
|
|
},
|
|
|
|
};
|
2019-01-11 16:48:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.errors.amount && new BigNumber(state.amount).lt(state.minAmount)) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.amount = {
|
|
|
|
...l10nMessages.TR_AMOUNT_IS_TOO_LOW_MINIMUM_AMOUNT_FOR_CREATING,
|
|
|
|
values: {
|
2019-05-15 12:20:00 +00:00
|
|
|
minimalAmount: state.minAmount,
|
2019-04-29 13:36:05 +00:00
|
|
|
networkSymbol: state.networkSymbol,
|
|
|
|
},
|
|
|
|
};
|
2019-01-11 16:48:32 +00:00
|
|
|
}
|
|
|
|
|
2018-09-22 16:49:05 +00:00
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
2018-12-28 15:15:18 +00:00
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Fee value validation
|
|
|
|
*/
|
|
|
|
export const feeValidation = ($state: State): PayloadAction<State> => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): State => {
|
2018-12-28 15:15:18 +00:00
|
|
|
const state = { ...$state };
|
|
|
|
if (!state.touched.fee) return state;
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const { network } = getState().selectedAccount;
|
2018-12-28 15:15:18 +00:00
|
|
|
if (!network) return state;
|
|
|
|
|
|
|
|
const { fee } = state;
|
|
|
|
if (fee.length < 1) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.fee = l10nMessages.TR_FEE_IS_NOT_SET;
|
2019-04-22 09:31:55 +00:00
|
|
|
} else if (fee.length > 0 && !validators.isAbs(fee)) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.fee = l10nMessages.TR_FEE_MUST_ME_AN_ABSOLUT_NUMBER;
|
2018-12-28 15:15:18 +00:00
|
|
|
} else {
|
|
|
|
const gl: BigNumber = new BigNumber(fee);
|
2019-02-18 10:45:40 +00:00
|
|
|
if (gl.isLessThan(network.fee.minFee)) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.fee = l10nMessages.TR_FEE_IS_BELOW_RECOMMENDED;
|
2019-02-18 10:45:40 +00:00
|
|
|
} else if (gl.isGreaterThan(network.fee.maxFee)) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.fee = l10nMessages.TR_FEE_IS_ABOVE_RECOMMENDED;
|
2018-12-28 15:15:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return state;
|
|
|
|
};
|
2019-01-09 13:31:40 +00:00
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* Destination Tag value validation
|
|
|
|
*/
|
2019-01-09 13:31:40 +00:00
|
|
|
export const destinationTagValidation = ($state: State): PayloadAction<State> => (): State => {
|
|
|
|
const state = { ...$state };
|
|
|
|
if (!state.touched.destinationTag) return state;
|
|
|
|
|
|
|
|
const { destinationTag } = state;
|
2019-04-16 14:05:13 +00:00
|
|
|
|
2019-04-22 09:31:55 +00:00
|
|
|
if (destinationTag.length > 0 && !validators.isAbs(destinationTag)) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.destinationTag = l10nMessages.TR_DESTINATION_TAG_MUST_BE_AN_ABSOLUTE;
|
2019-01-09 13:31:40 +00:00
|
|
|
}
|
2019-04-16 14:05:13 +00:00
|
|
|
|
|
|
|
if (parseInt(destinationTag, 10) > U_INT_32) {
|
2019-04-29 13:36:05 +00:00
|
|
|
state.errors.destinationTag = l10nMessages.TR_DESTINATION_TAG_IS_NOT_VALID;
|
2019-04-16 14:05:13 +00:00
|
|
|
}
|
|
|
|
|
2019-01-09 13:31:40 +00:00
|
|
|
return state;
|
|
|
|
};
|
2018-12-28 15:15:18 +00:00
|
|
|
|
2018-09-22 16:49:05 +00:00
|
|
|
/*
|
2019-03-04 12:33:02 +00:00
|
|
|
* UTILITIES
|
|
|
|
*/
|
2018-09-22 16:49:05 +00:00
|
|
|
|
2018-12-03 20:01:33 +00:00
|
|
|
const calculateTotal = (amount: string, fee: string): string => {
|
2018-09-22 16:49:05 +00:00
|
|
|
try {
|
2019-02-19 18:10:47 +00:00
|
|
|
const bAmount = new BigNumber(amount);
|
|
|
|
// BigNumber() returns NaN on non-numeric string
|
|
|
|
if (bAmount.isNaN()) {
|
|
|
|
throw new Error('Amount is not a number');
|
|
|
|
}
|
|
|
|
return bAmount.plus(fee).toFixed();
|
2018-09-22 16:49:05 +00:00
|
|
|
} catch (error) {
|
2019-05-23 11:34:08 +00:00
|
|
|
// TODO: empty input throws this error.
|
2018-09-22 16:49:05 +00:00
|
|
|
return '0';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-12-03 20:01:33 +00:00
|
|
|
const calculateMaxAmount = (balance: BigNumber, fee: string): string => {
|
2018-09-22 16:49:05 +00:00
|
|
|
try {
|
|
|
|
// TODO - minus pendings
|
|
|
|
const max = balance.minus(fee);
|
2019-02-18 10:45:40 +00:00
|
|
|
if (max.isLessThan(0)) return '0';
|
2019-01-08 10:50:30 +00:00
|
|
|
return max.toFixed();
|
2018-09-22 16:49:05 +00:00
|
|
|
} catch (error) {
|
2019-05-15 16:03:29 +00:00
|
|
|
console.error(error);
|
2018-09-22 16:49:05 +00:00
|
|
|
return '0';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-12-28 15:15:18 +00:00
|
|
|
// Generate FeeLevel dataset for "fee" select
|
2019-03-04 12:33:02 +00:00
|
|
|
export const getFeeLevels = (
|
|
|
|
feeLevels: Array<BlockchainFeeLevel>,
|
|
|
|
selected?: FeeLevel
|
|
|
|
): PayloadAction<Array<FeeLevel>> => (dispatch: Dispatch, getState: GetState): Array<FeeLevel> => {
|
2018-12-28 15:15:18 +00:00
|
|
|
const { network } = getState().selectedAccount;
|
|
|
|
if (!network) return []; // flowtype fallback
|
|
|
|
|
2019-04-29 13:36:05 +00:00
|
|
|
const l10nFeeMap = {
|
|
|
|
Low: l10nCommonMessages.TR_LOW_FEE,
|
|
|
|
Normal: l10nCommonMessages.TR_NORMAL_FEE,
|
|
|
|
High: l10nCommonMessages.TR_HIGH_FEE,
|
|
|
|
};
|
|
|
|
|
2018-12-28 15:15:18 +00:00
|
|
|
// map BlockchainFeeLevel to SendFormReducer FeeLevel
|
|
|
|
const levels = feeLevels.map(level => ({
|
|
|
|
value: level.name,
|
2019-04-29 13:51:55 +00:00
|
|
|
localizedValue: l10nFeeMap[level.value],
|
2018-12-28 15:15:18 +00:00
|
|
|
fee: level.value,
|
|
|
|
label: `${toDecimalAmount(level.value, network.decimals)} ${network.symbol}`,
|
|
|
|
}));
|
|
|
|
|
|
|
|
// add "Custom" level
|
2019-03-04 12:33:02 +00:00
|
|
|
const customLevel =
|
|
|
|
selected && selected.value === 'Custom'
|
|
|
|
? {
|
|
|
|
value: 'Custom',
|
2019-04-29 13:36:05 +00:00
|
|
|
localizedValue: l10nCommonMessages.TR_CUSTOM_FEE,
|
2019-03-04 12:33:02 +00:00
|
|
|
fee: selected.fee,
|
|
|
|
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
value: 'Custom',
|
2019-04-29 13:36:05 +00:00
|
|
|
localizedValue: l10nCommonMessages.TR_CUSTOM_FEE,
|
2019-03-04 12:33:02 +00:00
|
|
|
fee: '0',
|
|
|
|
label: '',
|
|
|
|
};
|
2018-12-28 15:15:18 +00:00
|
|
|
|
|
|
|
return levels.concat([customLevel]);
|
2018-12-05 13:12:23 +00:00
|
|
|
};
|
|
|
|
|
2018-09-22 16:49:05 +00:00
|
|
|
export const getSelectedFeeLevel = (feeLevels: Array<FeeLevel>, selected: FeeLevel): FeeLevel => {
|
|
|
|
const { value } = selected;
|
|
|
|
let selectedFeeLevel: ?FeeLevel;
|
|
|
|
selectedFeeLevel = feeLevels.find(f => f.value === value);
|
|
|
|
if (!selectedFeeLevel) {
|
|
|
|
// fallback to default
|
|
|
|
selectedFeeLevel = feeLevels.find(f => f.value === 'Normal');
|
|
|
|
}
|
|
|
|
return selectedFeeLevel || selected;
|
2018-12-03 20:01:33 +00:00
|
|
|
};
|