From 616949ba473a907e0c3fd00d0846d802a1bed06b Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 11 Jan 2019 17:47:36 +0100 Subject: [PATCH 1/6] add minAmount to SendformRippleReducer --- src/reducers/SendFormRippleReducer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/reducers/SendFormRippleReducer.js b/src/reducers/SendFormRippleReducer.js index f56083bc..f1c34ed3 100644 --- a/src/reducers/SendFormRippleReducer.js +++ b/src/reducers/SendFormRippleReducer.js @@ -21,6 +21,7 @@ export type State = { touched: {[k: string]: boolean}; address: string; amount: string; + minAmount: string; setMax: boolean; feeLevels: Array; selectedFeeLevel: FeeLevel; @@ -47,6 +48,7 @@ export const initialState: State = { touched: {}, address: '', amount: '', + minAmount: '0', setMax: false, feeLevels: [], selectedFeeLevel: { From aa87fa3b9f73d0f5f00986fa4118be8d9bd2c549 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 11 Jan 2019 17:48:32 +0100 Subject: [PATCH 2/6] validate address balance and set minAmount for ripple transaction --- src/actions/ripple/SendFormActions.js | 2 +- .../ripple/SendFormValidationActions.js | 55 ++++++++++++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/actions/ripple/SendFormActions.js b/src/actions/ripple/SendFormActions.js index ebe3fdc2..562326a4 100644 --- a/src/actions/ripple/SendFormActions.js +++ b/src/actions/ripple/SendFormActions.js @@ -60,7 +60,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction = } if (shouldUpdate) { - const validated = dispatch(ValidationActions.validation()); + const validated = dispatch(ValidationActions.validation(prevState.sendFormRipple)); dispatch({ type: SEND.VALIDATION, networkType: 'ripple', diff --git a/src/actions/ripple/SendFormValidationActions.js b/src/actions/ripple/SendFormValidationActions.js index 70ff0c54..4741ed49 100644 --- a/src/actions/ripple/SendFormValidationActions.js +++ b/src/actions/ripple/SendFormValidationActions.js @@ -1,5 +1,5 @@ /* @flow */ - +import TrezorConnect from 'trezor-connect'; import BigNumber from 'bignumber.js'; import * as SEND from 'actions/constants/send'; import { findDevice, getPendingAmount } from 'reducers/utils'; @@ -9,6 +9,7 @@ import type { Dispatch, GetState, PayloadAction, + PromiseAction, BlockchainFeeLevel, } from 'flowtype'; import type { State, FeeLevel } from 'reducers/SendFormRippleReducer'; @@ -59,7 +60,7 @@ export const onFeeUpdated = (network: string, feeLevels: Array => (dispatch: Dispatch, getState: GetState): State => { +export const validation = (prevState: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { // clone deep nested object // to avoid overrides across state history let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple)); @@ -74,6 +75,9 @@ export const validation = (): PayloadAction => (dispatch: Dispatch, getSt state = dispatch(amountValidation(state)); state = dispatch(feeValidation(state)); state = dispatch(destinationTagValidation(state)); + if (state.touched.address && prevState.address !== state.address) { + dispatch(addressBalanceValidation(state)); + } return state; }; @@ -135,6 +139,43 @@ const addressValidation = ($state: State): PayloadAction => (dispatch: Di return state; }; +/* +* Address balance validation +* Fetch data from trezor-connect and set minimum required amount in reducer +*/ +const addressBalanceValidation = ($state: State): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + 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 */ @@ -193,13 +234,21 @@ const amountValidation = ($state: State): PayloadAction => (dispatch: Dis state.errors.amount = 'Amount is not a number'; } else { const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol); - if (!state.amount.match(XRP_6_RE)) { state.errors.amount = 'Maximum 6 decimals allowed'; } else if (new BigNumber(state.total).greaterThan(new BigNumber(account.balance).minus(pendingAmount))) { state.errors.amount = 'Not enough funds'; } } + + if (!state.errors.amount && new BigNumber(account.balance).minus(state.total).lt(account.reserve)) { + state.errors.amount = `Not enough funds. Reserved amount for this account is ${account.reserve} ${state.networkSymbol}`; + } + + if (!state.errors.amount && new BigNumber(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; }; From 155f794bcb04da36baebccda49eaa21d2411629b Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 11 Jan 2019 17:48:44 +0100 Subject: [PATCH 3/6] remove comment --- src/actions/ripple/DiscoveryActions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/actions/ripple/DiscoveryActions.js b/src/actions/ripple/DiscoveryActions.js index 1fc41b68..01ef9288 100644 --- a/src/actions/ripple/DiscoveryActions.js +++ b/src/actions/ripple/DiscoveryActions.js @@ -77,6 +77,5 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover networkType: 'ripple', sequence: account.sequence, reserve: toDecimalAmount(account.reserve, network.decimals), - // reserve: '20', }; }; \ No newline at end of file From 9d325fb13940b899278c243258815437d5efbf5b Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 11 Jan 2019 17:55:36 +0100 Subject: [PATCH 4/6] flowtype fix --- src/actions/ripple/SendFormValidationActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/ripple/SendFormValidationActions.js b/src/actions/ripple/SendFormValidationActions.js index 4741ed49..bef1642d 100644 --- a/src/actions/ripple/SendFormValidationActions.js +++ b/src/actions/ripple/SendFormValidationActions.js @@ -225,7 +225,7 @@ const amountValidation = ($state: State): PayloadAction => (dispatch: Dis account, pending, } = getState().selectedAccount; - if (!account) return state; + if (!account || account.networkType !== 'ripple') return state; const { amount } = state; if (amount.length < 1) { From 249e5f98761748c94898b35cc21a551c4cd107af Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Sat, 12 Jan 2019 19:31:42 +0100 Subject: [PATCH 5/6] recalculate reserve while "setMax" --- src/actions/ripple/SendFormValidationActions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/ripple/SendFormValidationActions.js b/src/actions/ripple/SendFormValidationActions.js index bef1642d..4ee3a2f9 100644 --- a/src/actions/ripple/SendFormValidationActions.js +++ b/src/actions/ripple/SendFormValidationActions.js @@ -87,14 +87,14 @@ const recalculateTotalAmount = ($state: State): PayloadAction => (dispatc network, pending, } = getState().selectedAccount; - if (!account || !network) return $state; + if (!account || account.networkType !== 'ripple' || !network) return $state; const state = { ...$state }; const fee = toDecimalAmount(state.selectedFeeLevel.fee, network.decimals); if (state.setMax) { 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); } From 7eb1527c393da793425dfd57d781326995323735 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Sat, 12 Jan 2019 19:41:52 +0100 Subject: [PATCH 6/6] display reserve in RippleSendForm --- .../Wallet/views/Account/Send/ripple/index.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/views/Wallet/views/Account/Send/ripple/index.js b/src/views/Wallet/views/Account/Send/ripple/index.js index e9bc5e06..f6582587 100644 --- a/src/views/Wallet/views/Account/Send/ripple/index.js +++ b/src/views/Wallet/views/Account/Send/ripple/index.js @@ -22,6 +22,16 @@ import type { Props } from './Container'; // and put it inside config/variables.js 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` 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 tokensSelectValue = tokensSelectData[0]; const isAdvancedSettingsHidden = !advanced; + const accountReserve: ?string = account.networkType === 'ripple' && !account.empty ? account.reserve : null; return ( @@ -252,7 +263,14 @@ const AccountSend = (props: Props) => { autoCorrect="off" autoCapitalize="off" spellCheck="false" - topLabel="Amount" + topLabel={( + + Amount + {accountReserve && ( + Reserve: {accountReserve} {network.symbol} + )} + + )} value={amount} onChange={event => onAmountChange(event.target.value)} bottomText={errors.amount || warnings.amount || infos.amount}