From 26d788fe9cf9f88fcb4dea8feffbdab131eb57f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 25 Sep 2018 18:29:18 +0200 Subject: [PATCH 1/3] Ripple: handle case the destination does not exist --- src/bridge/RippleJSBridge.js | 46 ++++++++++++++++++++++++++++++++---- src/config/errors.js | 3 +++ static/i18n/en/errors.json | 3 +++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js index 952fdaf9..93747424 100644 --- a/src/bridge/RippleJSBridge.js +++ b/src/bridge/RippleJSBridge.js @@ -20,7 +20,7 @@ import { import FeesRippleKind from 'components/FeesField/RippleKind' import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName' -import { NotEnoughBalance } from 'config/errors' +import { NotEnoughBalance, NotEnoughBalanceBecauseDestinationNotCreated } from 'config/errors' import type { WalletBridge, EditProps } from './types' type Transaction = { @@ -114,7 +114,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera } } -function isRecipientValid(currency, recipient) { +function isRecipientValid(recipient) { try { bs58check.decode(recipient) return true @@ -241,6 +241,32 @@ const getServerInfo = (map => endpointConfig => { return f() })({}) +const recipientIsNew = async (endpointConfig, recipient) => { + if (!isRecipientValid(recipient)) return false + const api = apiForEndpointConfig(endpointConfig) + try { + await api.connect() + try { + await api.getAccountInfo(recipient) + return false + } catch (e) { + if (e.message !== 'actNotFound') { + throw e + } + return true + } + } finally { + api.disconnect() + } +} + +const cacheRecipientsNew = {} +const cachedRecipientIsNew= (endpointConfig, recipient) => { + if (recipient in cacheRecipientsNew) return cacheRecipientsNew[recipient] + return (cacheRecipientsNew[recipient] = recipientIsNew(endpointConfig, recipient)) +} + + const RippleJSBridge: WalletBridge = { scanAccountsOnDevice: (currency, deviceId) => Observable.create(o => { @@ -446,7 +472,7 @@ const RippleJSBridge: WalletBridge = { pullMoreOperations: () => Promise.resolve(a => a), // FIXME not implemented - isRecipientValid: (currency, recipient) => Promise.resolve(isRecipientValid(currency, recipient)), + isRecipientValid: (currency, recipient) => Promise.resolve(isRecipientValid(recipient)), getRecipientWarning: () => Promise.resolve(null), createTransaction: () => ({ @@ -496,10 +522,21 @@ const RippleJSBridge: WalletBridge = { checkValidTransaction: async (a, t) => { const r = await getServerInfo(a.endpointConfig) + const reserveBaseXRP = parseAPIValue(r.validatedLedger.reserveBaseXRP) + if (t.recipient) { + if (await cachedRecipientIsNew(a.endpointConfig, t.recipient)) { + if (t.amount.lt(reserveBaseXRP)) { + const f = formatAPICurrencyXRP(reserveBaseXRP) + throw new NotEnoughBalanceBecauseDestinationNotCreated('', { + minimalAmount: `${f.currency} ${f.value}`, + }) + } + } + } if ( t.amount .plus(t.fee) - .plus(parseAPIValue(r.validatedLedger.reserveBaseXRP)) + .plus(reserveBaseXRP) .isLessThanOrEqualTo(a.balance) ) { return true @@ -513,6 +550,7 @@ const RippleJSBridge: WalletBridge = { signAndBroadcast: (a, t, deviceId) => Observable.create(o => { + delete cacheRecipientsNew[t.recipient] let cancelled = false const isCancelled = () => cancelled const onSigned = () => { diff --git a/src/config/errors.js b/src/config/errors.js index f6e4a81c..a40a3a73 100644 --- a/src/config/errors.js +++ b/src/config/errors.js @@ -30,6 +30,9 @@ export const ManagerUninstallBTCDep = createCustomErrorClass('ManagerUninstallBT export const NetworkDown = createCustomErrorClass('NetworkDown') export const NoAddressesFound = createCustomErrorClass('NoAddressesFound') export const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance') +export const NotEnoughBalanceBecauseDestinationNotCreated = createCustomErrorClass( + 'NotEnoughBalanceBecauseDestinationNotCreated', +) export const PasswordsDontMatchError = createCustomErrorClass('PasswordsDontMatch') export const PasswordIncorrectError = createCustomErrorClass('PasswordIncorrect') export const TimeoutTagged = createCustomErrorClass('TimeoutTagged') diff --git a/static/i18n/en/errors.json b/static/i18n/en/errors.json index 043c7038..5af4d119 100644 --- a/static/i18n/en/errors.json +++ b/static/i18n/en/errors.json @@ -99,6 +99,9 @@ "title": "Oops, insufficient balance", "description": "Make sure the account to debit has sufficient balance" }, + "NotEnoughBalanceBecauseDestinationNotCreated": { + "title": "Destination does not exist. Send at least {{minimalAmount}}" + }, "PasswordsDontMatch": { "title": "Passwords don't match", "description": "Please try again" From ee2631f028a01b8d3df74559291440d9794ab05b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 25 Sep 2018 18:50:28 +0200 Subject: [PATCH 2/3] Prettier --- .prettierignore | 1 + src/bridge/RippleJSBridge.js | 3 +-- test-e2e/sync/sync-accounts.spec.js | 16 +++++++--------- test-e2e/sync/wait-sync.js | 6 +++++- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.prettierignore b/.prettierignore index ec6d3cdd..f8564104 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ package.json +test-e2e/**/*.json diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js index 93747424..24949435 100644 --- a/src/bridge/RippleJSBridge.js +++ b/src/bridge/RippleJSBridge.js @@ -261,12 +261,11 @@ const recipientIsNew = async (endpointConfig, recipient) => { } const cacheRecipientsNew = {} -const cachedRecipientIsNew= (endpointConfig, recipient) => { +const cachedRecipientIsNew = (endpointConfig, recipient) => { if (recipient in cacheRecipientsNew) return cacheRecipientsNew[recipient] return (cacheRecipientsNew[recipient] = recipientIsNew(endpointConfig, recipient)) } - const RippleJSBridge: WalletBridge = { scanAccountsOnDevice: (currency, deviceId) => Observable.create(o => { diff --git a/test-e2e/sync/sync-accounts.spec.js b/test-e2e/sync/sync-accounts.spec.js index b67c505d..d04c5c7b 100644 --- a/test-e2e/sync/sync-accounts.spec.js +++ b/test-e2e/sync/sync-accounts.spec.js @@ -46,15 +46,13 @@ function getSanitized(filePath) { const data = require(`${filePath}`) // eslint-disable-line import/no-dynamic-require const accounts = data.data.accounts.map(a => a.data) accounts.sort(ACCOUNT_SORT) - return accounts - .map(a => pick(a, ACCOUNTS_FIELDS)) - .map(a => { - a.operations.sort(OP_SORT) - return { - ...a, - operations: a.operations.map(o => pick(o, OPS_FIELDS)), - } - }) + return accounts.map(a => pick(a, ACCOUNTS_FIELDS)).map(a => { + a.operations.sort(OP_SORT) + return { + ...a, + operations: a.operations.map(o => pick(o, OPS_FIELDS)), + } + }) } function getOpHash(op) { diff --git a/test-e2e/sync/wait-sync.js b/test-e2e/sync/wait-sync.js index 79b588af..5ddb2ae6 100644 --- a/test-e2e/sync/wait-sync.js +++ b/test-e2e/sync/wait-sync.js @@ -27,7 +27,11 @@ async function waitForSync() { const areAllSync = mapped.every(account => { const diff = now - new Date(account.lastSyncDate).getTime() if (diff <= MIN_TIME_DIFF) return true - console.log(`[${account.name}] synced ${moment(account.lastSyncDate).fromNow()} (${moment(account.lastSyncDate).format('YYYY-MM-DD HH:mm:ss')})`) + console.log( + `[${account.name}] synced ${moment(account.lastSyncDate).fromNow()} (${moment( + account.lastSyncDate, + ).format('YYYY-MM-DD HH:mm:ss')})`, + ) return false }) return areAllSync From 6366d69a9516a3354228dd59e34ad221849d0d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Wed, 26 Sep 2018 14:04:15 +0200 Subject: [PATCH 3/3] Update errors.json --- static/i18n/en/errors.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/i18n/en/errors.json b/static/i18n/en/errors.json index 5af4d119..8acef277 100644 --- a/static/i18n/en/errors.json +++ b/static/i18n/en/errors.json @@ -100,7 +100,7 @@ "description": "Make sure the account to debit has sufficient balance" }, "NotEnoughBalanceBecauseDestinationNotCreated": { - "title": "Destination does not exist. Send at least {{minimalAmount}}" + "title": "Recipient address is inactive. Send at least {{minimalAmount}} to activate it" }, "PasswordsDontMatch": { "title": "Passwords don't match",