mirror of
https://github.com/trezor/trezor-wallet
synced 2025-03-03 17:56:06 +00:00
Merge pull request #328 from trezor/fix/ripple-amount-validation
Fix/ripple amount validation
This commit is contained in:
commit
bb9d59bece
@ -77,6 +77,5 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover
|
|||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
sequence: account.sequence,
|
sequence: account.sequence,
|
||||||
reserve: toDecimalAmount(account.reserve, network.decimals),
|
reserve: toDecimalAmount(account.reserve, network.decimals),
|
||||||
// reserve: '20',
|
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -60,7 +60,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
const validated = dispatch(ValidationActions.validation());
|
const validated = dispatch(ValidationActions.validation(prevState.sendFormRipple));
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.VALIDATION,
|
type: SEND.VALIDATION,
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
import TrezorConnect from 'trezor-connect';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import * as SEND from 'actions/constants/send';
|
import * as SEND from 'actions/constants/send';
|
||||||
import { findDevice, getPendingAmount } from 'reducers/utils';
|
import { findDevice, getPendingAmount } from 'reducers/utils';
|
||||||
@ -9,6 +9,7 @@ import type {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
GetState,
|
||||||
PayloadAction,
|
PayloadAction,
|
||||||
|
PromiseAction,
|
||||||
BlockchainFeeLevel,
|
BlockchainFeeLevel,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
||||||
@ -59,7 +60,7 @@ export const onFeeUpdated = (network: string, feeLevels: Array<BlockchainFeeLeve
|
|||||||
/*
|
/*
|
||||||
* Recalculate amount, total and fees
|
* Recalculate amount, total and fees
|
||||||
*/
|
*/
|
||||||
export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const validation = (prevState: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
||||||
// clone deep nested object
|
// clone deep nested object
|
||||||
// to avoid overrides across state history
|
// to avoid overrides across state history
|
||||||
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
|
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
|
||||||
@ -74,6 +75,9 @@ export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getSt
|
|||||||
state = dispatch(amountValidation(state));
|
state = dispatch(amountValidation(state));
|
||||||
state = dispatch(feeValidation(state));
|
state = dispatch(feeValidation(state));
|
||||||
state = dispatch(destinationTagValidation(state));
|
state = dispatch(destinationTagValidation(state));
|
||||||
|
if (state.touched.address && prevState.address !== state.address) {
|
||||||
|
dispatch(addressBalanceValidation(state));
|
||||||
|
}
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,14 +87,14 @@ const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatc
|
|||||||
network,
|
network,
|
||||||
pending,
|
pending,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
if (!account || !network) return $state;
|
if (!account || account.networkType !== 'ripple' || !network) return $state;
|
||||||
|
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
const fee = toDecimalAmount(state.selectedFeeLevel.fee, network.decimals);
|
const fee = toDecimalAmount(state.selectedFeeLevel.fee, network.decimals);
|
||||||
|
|
||||||
if (state.setMax) {
|
if (state.setMax) {
|
||||||
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
||||||
const availableBalance = new BigNumber(account.balance).minus(pendingAmount);
|
const availableBalance = new BigNumber(account.balance).minus(account.reserve).minus(pendingAmount);
|
||||||
state.amount = calculateMaxAmount(availableBalance, fee);
|
state.amount = calculateMaxAmount(availableBalance, fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +139,43 @@ const addressValidation = ($state: State): PayloadAction<State> => (dispatch: Di
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Address balance validation
|
||||||
|
* Fetch data from trezor-connect and set minimum required amount in reducer
|
||||||
|
*/
|
||||||
|
const addressBalanceValidation = ($state: State): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
const { network } = getState().selectedAccount;
|
||||||
|
if (!network) return;
|
||||||
|
|
||||||
|
let minAmount: string = '0';
|
||||||
|
const response = await TrezorConnect.rippleGetAccountInfo({
|
||||||
|
account: {
|
||||||
|
descriptor: $state.address,
|
||||||
|
},
|
||||||
|
coin: network.shortcut,
|
||||||
|
});
|
||||||
|
if (response.success) {
|
||||||
|
const empty = response.payload.sequence <= 0 && response.payload.balance === '0';
|
||||||
|
if (empty) {
|
||||||
|
minAmount = toDecimalAmount(response.payload.reserve, network.decimals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Address label assignation
|
* Address label assignation
|
||||||
*/
|
*/
|
||||||
@ -184,7 +225,7 @@ const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dis
|
|||||||
account,
|
account,
|
||||||
pending,
|
pending,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
if (!account) return state;
|
if (!account || account.networkType !== 'ripple') return state;
|
||||||
|
|
||||||
const { amount } = state;
|
const { amount } = state;
|
||||||
if (amount.length < 1) {
|
if (amount.length < 1) {
|
||||||
@ -193,13 +234,21 @@ const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dis
|
|||||||
state.errors.amount = 'Amount is not a number';
|
state.errors.amount = 'Amount is not a number';
|
||||||
} else {
|
} else {
|
||||||
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
|
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
|
||||||
|
|
||||||
if (!state.amount.match(XRP_6_RE)) {
|
if (!state.amount.match(XRP_6_RE)) {
|
||||||
state.errors.amount = 'Maximum 6 decimals allowed';
|
state.errors.amount = 'Maximum 6 decimals allowed';
|
||||||
} else if (new BigNumber(state.total).greaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
|
} else if (new BigNumber(state.total).greaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
|
||||||
state.errors.amount = 'Not enough funds';
|
state.errors.amount = 'Not enough funds';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!state.errors.amount && new BigNumber(account.balance).minus(state.total).lt(account.reserve)) {
|
||||||
|
state.errors.amount = `Not enough funds. Reserved amount for this account is ${account.reserve} ${state.networkSymbol}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.errors.amount && new BigNumber(state.amount).lt(state.minAmount)) {
|
||||||
|
state.errors.amount = `Amount is too low. Minimum amount for creating a new account is ${state.minAmount} ${state.networkSymbol}`;
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ export type State = {
|
|||||||
touched: {[k: string]: boolean};
|
touched: {[k: string]: boolean};
|
||||||
address: string;
|
address: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
|
minAmount: string;
|
||||||
setMax: boolean;
|
setMax: boolean;
|
||||||
feeLevels: Array<FeeLevel>;
|
feeLevels: Array<FeeLevel>;
|
||||||
selectedFeeLevel: FeeLevel;
|
selectedFeeLevel: FeeLevel;
|
||||||
@ -47,6 +48,7 @@ export const initialState: State = {
|
|||||||
touched: {},
|
touched: {},
|
||||||
address: '',
|
address: '',
|
||||||
amount: '',
|
amount: '',
|
||||||
|
minAmount: '0',
|
||||||
setMax: false,
|
setMax: false,
|
||||||
feeLevels: [],
|
feeLevels: [],
|
||||||
selectedFeeLevel: {
|
selectedFeeLevel: {
|
||||||
|
@ -22,6 +22,16 @@ import type { Props } from './Container';
|
|||||||
// and put it inside config/variables.js
|
// and put it inside config/variables.js
|
||||||
const SmallScreenWidth = '850px';
|
const SmallScreenWidth = '850px';
|
||||||
|
|
||||||
|
const AmountInputLabelWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AmountInputLabel = styled.span`
|
||||||
|
text-align: right;
|
||||||
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
`;
|
||||||
|
|
||||||
const InputRow = styled.div`
|
const InputRow = styled.div`
|
||||||
padding-bottom: 28px;
|
padding-bottom: 28px;
|
||||||
`;
|
`;
|
||||||
@ -228,6 +238,7 @@ const AccountSend = (props: Props) => {
|
|||||||
const tokensSelectData: Array<{ value: string, label: string }> = [{ value: network.symbol, label: network.symbol }];
|
const tokensSelectData: Array<{ value: string, label: string }> = [{ value: network.symbol, label: network.symbol }];
|
||||||
const tokensSelectValue = tokensSelectData[0];
|
const tokensSelectValue = tokensSelectData[0];
|
||||||
const isAdvancedSettingsHidden = !advanced;
|
const isAdvancedSettingsHidden = !advanced;
|
||||||
|
const accountReserve: ?string = account.networkType === 'ripple' && !account.empty ? account.reserve : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Content>
|
<Content>
|
||||||
@ -252,7 +263,14 @@ const AccountSend = (props: Props) => {
|
|||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
topLabel="Amount"
|
topLabel={(
|
||||||
|
<AmountInputLabelWrapper>
|
||||||
|
<AmountInputLabel>Amount</AmountInputLabel>
|
||||||
|
{accountReserve && (
|
||||||
|
<AmountInputLabel>Reserve: {accountReserve} {network.symbol}</AmountInputLabel>
|
||||||
|
)}
|
||||||
|
</AmountInputLabelWrapper>
|
||||||
|
)}
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={event => onAmountChange(event.target.value)}
|
onChange={event => onAmountChange(event.target.value)}
|
||||||
bottomText={errors.amount || warnings.amount || infos.amount}
|
bottomText={errors.amount || warnings.amount || infos.amount}
|
||||||
|
Loading…
Reference in New Issue
Block a user