Browse Source

Merge branch 'develop' into fees-failsafe

gre-patch-1
Gaëtan Renaudeau 6 years ago
committed by GitHub
parent
commit
f0035f22fa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .prettierignore
  2. 2
      package.json
  3. 45
      src/bridge/RippleJSBridge.js
  4. 2
      src/commands/index.js
  5. 18
      src/commands/killInternalProcess.js
  6. 5
      src/components/ExchangePage/index.js
  7. 62
      src/components/ManagerPage/AppsList.js
  8. 7
      src/components/ManagerPage/index.js
  9. 2
      src/components/Onboarding/steps/Analytics.js
  10. 1
      src/components/base/Modal/ConfirmModal.js
  11. 3
      src/config/errors.js
  12. 29
      src/helpers/countervalues.js
  13. 4
      src/helpers/reset.js
  14. 2
      src/reducers/settings.js
  15. 2
      static/i18n/en/app.json
  16. 3
      static/i18n/en/errors.json
  17. 16
      test-e2e/sync/sync-accounts.spec.js
  18. 6
      test-e2e/sync/wait-sync.js
  19. 6
      yarn.lock

1
.prettierignore

@ -1 +1,2 @@
package.json
test-e2e/**/*.json

2
package.json

@ -40,7 +40,7 @@
"@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "4.22.0",
"@ledgerhq/ledger-core": "2.0.0-rc.7",
"@ledgerhq/live-common": "^3.5.1",
"@ledgerhq/live-common": "^3.7.1",
"animated": "^0.2.2",
"async": "^2.6.1",
"axios": "^0.18.0",

45
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, FeeNotLoaded } from 'config/errors'
import { NotEnoughBalance, FeeNotLoaded, NotEnoughBalanceBecauseDestinationNotCreated } from 'config/errors'
import type { WalletBridge, EditProps } from './types'
type Transaction = {
@ -116,7 +116,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera
}
}
function isRecipientValid(currency, recipient) {
function isRecipientValid(recipient) {
try {
bs58check.decode(recipient)
return true
@ -243,6 +243,31 @@ 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<Transaction> = {
scanAccountsOnDevice: (currency, deviceId) =>
Observable.create(o => {
@ -448,7 +473,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
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: () => ({
@ -499,10 +524,21 @@ const RippleJSBridge: WalletBridge<Transaction> = {
checkValidTransaction: async (a, t) => {
if (!t.fee) throw new FeeNotLoaded()
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 || 0)
.plus(parseAPIValue(r.validatedLedger.reserveBaseXRP))
.plus(reserveBaseXRP)
.isLessThanOrEqualTo(a.balance)
) {
return true
@ -516,6 +552,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
signAndBroadcast: (a, t, deviceId) =>
Observable.create(o => {
delete cacheRecipientsNew[t.recipient]
let cancelled = false
const isCancelled = () => cancelled
const onSigned = () => {

2
src/commands/index.js

@ -15,6 +15,7 @@ import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu'
import installOsuFirmware from 'commands/installOsuFirmware'
import isDashboardOpen from 'commands/isDashboardOpen'
import killInternalProcess from 'commands/killInternalProcess'
import libcoreGetFees from 'commands/libcoreGetFees'
import libcoreGetVersion from 'commands/libcoreGetVersion'
import libcoreScanAccounts from 'commands/libcoreScanAccounts'
@ -47,6 +48,7 @@ const all: Array<Command<any, any>> = [
installMcu,
installOsuFirmware,
isDashboardOpen,
killInternalProcess,
libcoreGetFees,
libcoreGetVersion,
libcoreScanAccounts,

18
src/commands/killInternalProcess.js

@ -0,0 +1,18 @@
// @flow
import { createCommand, Command } from 'helpers/ipc'
import { of } from 'rxjs'
type Input = void
type Result = boolean
const cmd: Command<Input, Result> = createCommand('killInternalProcess', () => {
setTimeout(() => {
// we assume commands are run on the internal process
// special exit code for better identification
process.exit(42)
})
return of(true)
})
export default cmd

5
src/components/ExchangePage/index.js

@ -2,6 +2,7 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import shuffle from 'lodash/shuffle'
import type { T } from 'types/common'
import { urls } from 'config/urls'
@ -21,7 +22,7 @@ type Props = {
t: T,
}
const cards = [
const cards = shuffle([
{
key: 'coinhouse',
id: 'coinhouse',
@ -70,7 +71,7 @@ const cards = [
url: urls.genesis,
logo: <img src={i('logos/exchanges/genesis.svg')} alt="Genesis" width={150} />,
},
]
])
class ExchangePage extends PureComponent<Props> {
render() {

62
src/components/ManagerPage/AppsList.js

@ -7,15 +7,13 @@ import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { compose } from 'redux'
import type { Device, T } from 'types/common'
import type { Application, ApplicationVersion, DeviceInfo } from 'helpers/types'
import type { ApplicationVersion, DeviceInfo } from 'helpers/types'
import { getFullListSortedCryptoCurrencies } from 'helpers/countervalues'
import { developerModeSelector } from 'reducers/settings'
import listApps from 'commands/listApps'
import listAppVersions from 'commands/listAppVersions'
import installApp from 'commands/installApp'
import uninstallApp from 'commands/uninstallApp'
import Box from 'components/base/Box'
import Space from 'components/base/Space'
import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal'
@ -26,13 +24,11 @@ import Spinner from 'components/base/Spinner'
import Button from 'components/base/Button'
import TranslatedError from 'components/TranslatedError'
import TrackPage from 'analytics/TrackPage'
import IconInfoCircle from 'icons/InfoCircle'
import ExclamationCircleThin from 'icons/ExclamationCircleThin'
import Update from 'icons/Update'
import Trash from 'icons/Trash'
import CheckCircle from 'icons/CheckCircle'
import { FreezeDeviceChangeEvents } from './HookDeviceChange'
import ManagerApp, { Container as FakeManagerAppContainer } from './ManagerApp'
import AppSearchBar from './AppSearchBar'
@ -102,29 +98,53 @@ class AppsList extends PureComponent<Props, State> {
_unmounted = false
filterAppVersions = (applicationsList, compatibleAppVersionsList) => {
if (!this.props.isDevMode) {
return compatibleAppVersionsList.filter(version => {
const app = applicationsList.find(e => e.id === version.app)
if (app) {
return app.category !== 2
}
prepareAppList = ({ applicationsList, compatibleAppVersionsList, sortedCryptoCurrencies }) => {
const filtered = this.props.isDevMode
? compatibleAppVersionsList.slice(0)
: compatibleAppVersionsList.filter(version => {
const app = applicationsList.find(e => e.id === version.app)
if (app) {
return app.category !== 2
}
return false
})
}
return compatibleAppVersionsList
return false
})
const sortedCryptoApps = []
// sort by crypto first
sortedCryptoCurrencies.forEach(crypto => {
const app = filtered.find(
item => item.name.toLowerCase() === crypto.managerAppName.toLowerCase(),
)
if (app) {
filtered.splice(filtered.indexOf(app), 1)
sortedCryptoApps.push(app)
}
})
return sortedCryptoApps.concat(filtered)
}
async fetchAppList() {
try {
const { deviceInfo } = this.props
const applicationsList: Array<Application> = await listApps.send().toPromise()
const compatibleAppVersionsList = await listAppVersions.send(deviceInfo).toPromise()
const filteredAppVersionsList = this.filterAppVersions(
const [
applicationsList,
compatibleAppVersionsList,
)
sortedCryptoCurrencies,
] = await Promise.all([
listApps.send().toPromise(),
listAppVersions.send(deviceInfo).toPromise(),
getFullListSortedCryptoCurrencies(),
])
const filteredAppVersionsList = this.prepareAppList({
applicationsList,
compatibleAppVersionsList,
sortedCryptoCurrencies,
})
if (!this._unmounted) {
this.setState({

7
src/components/ManagerPage/index.js

@ -4,12 +4,11 @@ import React, { PureComponent, Fragment } from 'react'
import invariant from 'invariant'
import { openURL } from 'helpers/linking'
import { urls } from 'config/urls'
import type { Device } from 'types/common'
import type { DeviceInfo } from 'helpers/types'
import { getFullListSortedCryptoCurrencies } from 'helpers/countervalues'
import Dashboard from './Dashboard'
import ManagerGenuineCheck from './ManagerGenuineCheck'
import HookDeviceChange from './HookDeviceChange'
@ -30,6 +29,10 @@ const INITIAL_STATE = {
class ManagerPage extends PureComponent<Props, State> {
state = INITIAL_STATE
componentDidMount() {
getFullListSortedCryptoCurrencies() // start fetching the crypto currencies ordering
}
// prettier-ignore
handleSuccessGenuine = ({ device, deviceInfo }: { device: Device, deviceInfo: DeviceInfo }) => { // eslint-disable-line react/no-unused-prop-types
this.setState({ isGenuine: true, device, deviceInfo })

2
src/components/Onboarding/steps/Analytics.js

@ -26,7 +26,7 @@ type State = {
}
const INITIAL_STATE = {
analyticsToggle: false,
analyticsToggle: true,
sentryLogsToggle: true,
}

1
src/components/base/Modal/ConfirmModal.js

@ -79,6 +79,7 @@ class ConfirmModal extends PureComponent<Props> {
primary={!isDanger}
danger={isDanger}
isLoading={isLoading}
disabled={isLoading}
>
{realConfirmText}
</Button>

3
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')

29
src/helpers/countervalues.js

@ -12,6 +12,8 @@ import {
intermediaryCurrency,
} from 'reducers/settings'
import logger from 'logger'
import { listCryptoCurrencies } from '@ledgerhq/live-common/lib/helpers/currencies'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
const pairsSelector = createSelector(
currenciesSelector,
@ -52,6 +54,7 @@ const addExtraPollingHooks = (schedulePoll, cancelPoll) => {
}
}
// TODO we should be able to pass-in our network() function
const CounterValues = createCounterValues({
log: (...args) => logger.log('CounterValues:', ...args),
getAPIBaseURL: () => LEDGER_COUNTERVALUES_API,
@ -61,4 +64,30 @@ const CounterValues = createCounterValues({
addExtraPollingHooks,
})
let sortCache
export const getFullListSortedCryptoCurrencies: () => Promise<CryptoCurrency[]> = () => {
if (!sortCache) {
sortCache = CounterValues.fetchTickersByMarketcap().then(
tickers => {
const list = listCryptoCurrencies().slice(0)
const prependList = []
tickers.forEach(ticker => {
const item = list.find(c => c.ticker === ticker)
if (item) {
list.splice(list.indexOf(item), 1)
prependList.push(item)
}
})
return prependList.concat(list)
},
() => {
sortCache = null // reset the cache for the next time it comes here to "try again"
return listCryptoCurrencies() // fallback on default sort
},
)
}
return sortCache
}
export default CounterValues

4
src/helpers/reset.js

@ -6,8 +6,10 @@ import resolveUserDataDirectory from 'helpers/resolveUserDataDirectory'
import { disable as disableDBMiddleware } from 'middlewares/db'
import db from 'helpers/db'
import { delay } from 'helpers/promise'
import killInternalProcess from 'commands/killInternalProcess'
function resetLibcoreDatabase() {
async function resetLibcoreDatabase() {
await killInternalProcess.send().toPromise()
const dbpath = path.resolve(resolveUserDataDirectory(), 'sqlite/')
rimraf.sync(dbpath, { glob: false })
}

2
src/reducers/settings.js

@ -70,7 +70,7 @@ const INITIAL_STATE: SettingsState = {
currenciesSettings: {},
developerMode: !!process.env.__DEV__,
loaded: false,
shareAnalytics: false,
shareAnalytics: true,
sentryLogs: true,
lastUsedVersion: __APP_VERSION__,
}

2
static/i18n/en/app.json

@ -167,7 +167,7 @@
"coinmama": "Coinmama is a financial service that makes it fast, safe and fun to buy digital assets, anywhere in the world.",
"simplex": "Simplex is a EU licensed financial institution, providing a fraudless credit card payment solution.",
"paybis": "it is safe and easy to Buy Bitcoin with credit card from PayBis. Service operates in US, Canada, Germany, Russia and Saudi Arabia.",
"luno": "Luno makes it safe and easy to buy, store and learn about digital currencies like Bitcoin and Ethreum",
"luno": "Luno makes it safe and easy to buy, store and learn about cryptocurrencies like Bitcoin and Ethereum",
"shapeshift": "ShapeShift is an online marketplace where users can buy and sell digital assets. It is a fast and secure way for the world to buy and sell digital assets, with no lengthy signup process, no counterparty risk, and no friction.",
"genesis": "Genesis is an institutional trading firm offering liquidity and borrow for digital currencies, including bitcoin, bitcoin cash, ethereum, ethereum classic, litecoin, and XRP."
},

3
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": "Recipient address is inactive. Send at least {{minimalAmount}} to activate it"
},
"PasswordsDontMatch": {
"title": "Passwords don't match",
"description": "Please try again"

16
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) {

6
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

6
yarn.lock

@ -1549,9 +1549,9 @@
npm "^5.7.1"
prebuild-install "^2.2.2"
"@ledgerhq/live-common@^3.5.1":
version "3.5.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.5.1.tgz#dab3eb061f361999a9e04ef564808831faac61ea"
"@ledgerhq/live-common@^3.7.1":
version "3.7.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.7.1.tgz#5ce1895920d2eae6c454c2c72612dc9afd11adec"
dependencies:
axios "^0.18.0"
bignumber.js "^7.2.1"

Loading…
Cancel
Save