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 package.json
test-e2e/**/*.json

2
package.json

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

45
src/bridge/RippleJSBridge.js

@ -20,7 +20,7 @@ import {
import FeesRippleKind from 'components/FeesField/RippleKind' import FeesRippleKind from 'components/FeesField/RippleKind'
import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind' import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName' 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' import type { WalletBridge, EditProps } from './types'
type Transaction = { type Transaction = {
@ -116,7 +116,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera
} }
} }
function isRecipientValid(currency, recipient) { function isRecipientValid(recipient) {
try { try {
bs58check.decode(recipient) bs58check.decode(recipient)
return true return true
@ -243,6 +243,31 @@ const getServerInfo = (map => endpointConfig => {
return f() 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> = { const RippleJSBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice: (currency, deviceId) => scanAccountsOnDevice: (currency, deviceId) =>
Observable.create(o => { Observable.create(o => {
@ -448,7 +473,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
pullMoreOperations: () => Promise.resolve(a => a), // FIXME not implemented 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), getRecipientWarning: () => Promise.resolve(null),
createTransaction: () => ({ createTransaction: () => ({
@ -499,10 +524,21 @@ const RippleJSBridge: WalletBridge<Transaction> = {
checkValidTransaction: async (a, t) => { checkValidTransaction: async (a, t) => {
if (!t.fee) throw new FeeNotLoaded() if (!t.fee) throw new FeeNotLoaded()
const r = await getServerInfo(a.endpointConfig) 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 ( if (
t.amount t.amount
.plus(t.fee || 0) .plus(t.fee || 0)
.plus(parseAPIValue(r.validatedLedger.reserveBaseXRP)) .plus(reserveBaseXRP)
.isLessThanOrEqualTo(a.balance) .isLessThanOrEqualTo(a.balance)
) { ) {
return true return true
@ -516,6 +552,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
signAndBroadcast: (a, t, deviceId) => signAndBroadcast: (a, t, deviceId) =>
Observable.create(o => { Observable.create(o => {
delete cacheRecipientsNew[t.recipient]
let cancelled = false let cancelled = false
const isCancelled = () => cancelled const isCancelled = () => cancelled
const onSigned = () => { const onSigned = () => {

2
src/commands/index.js

@ -15,6 +15,7 @@ import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu' import installMcu from 'commands/installMcu'
import installOsuFirmware from 'commands/installOsuFirmware' import installOsuFirmware from 'commands/installOsuFirmware'
import isDashboardOpen from 'commands/isDashboardOpen' import isDashboardOpen from 'commands/isDashboardOpen'
import killInternalProcess from 'commands/killInternalProcess'
import libcoreGetFees from 'commands/libcoreGetFees' import libcoreGetFees from 'commands/libcoreGetFees'
import libcoreGetVersion from 'commands/libcoreGetVersion' import libcoreGetVersion from 'commands/libcoreGetVersion'
import libcoreScanAccounts from 'commands/libcoreScanAccounts' import libcoreScanAccounts from 'commands/libcoreScanAccounts'
@ -47,6 +48,7 @@ const all: Array<Command<any, any>> = [
installMcu, installMcu,
installOsuFirmware, installOsuFirmware,
isDashboardOpen, isDashboardOpen,
killInternalProcess,
libcoreGetFees, libcoreGetFees,
libcoreGetVersion, libcoreGetVersion,
libcoreScanAccounts, 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 React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import shuffle from 'lodash/shuffle'
import type { T } from 'types/common' import type { T } from 'types/common'
import { urls } from 'config/urls' import { urls } from 'config/urls'
@ -21,7 +22,7 @@ type Props = {
t: T, t: T,
} }
const cards = [ const cards = shuffle([
{ {
key: 'coinhouse', key: 'coinhouse',
id: 'coinhouse', id: 'coinhouse',
@ -70,7 +71,7 @@ const cards = [
url: urls.genesis, url: urls.genesis,
logo: <img src={i('logos/exchanges/genesis.svg')} alt="Genesis" width={150} />, logo: <img src={i('logos/exchanges/genesis.svg')} alt="Genesis" width={150} />,
}, },
] ])
class ExchangePage extends PureComponent<Props> { class ExchangePage extends PureComponent<Props> {
render() { render() {

62
src/components/ManagerPage/AppsList.js

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

7
src/components/ManagerPage/index.js

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

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

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

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

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

3
src/config/errors.js

@ -30,6 +30,9 @@ export const ManagerUninstallBTCDep = createCustomErrorClass('ManagerUninstallBT
export const NetworkDown = createCustomErrorClass('NetworkDown') export const NetworkDown = createCustomErrorClass('NetworkDown')
export const NoAddressesFound = createCustomErrorClass('NoAddressesFound') export const NoAddressesFound = createCustomErrorClass('NoAddressesFound')
export const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance') export const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')
export const NotEnoughBalanceBecauseDestinationNotCreated = createCustomErrorClass(
'NotEnoughBalanceBecauseDestinationNotCreated',
)
export const PasswordsDontMatchError = createCustomErrorClass('PasswordsDontMatch') export const PasswordsDontMatchError = createCustomErrorClass('PasswordsDontMatch')
export const PasswordIncorrectError = createCustomErrorClass('PasswordIncorrect') export const PasswordIncorrectError = createCustomErrorClass('PasswordIncorrect')
export const TimeoutTagged = createCustomErrorClass('TimeoutTagged') export const TimeoutTagged = createCustomErrorClass('TimeoutTagged')

29
src/helpers/countervalues.js

@ -12,6 +12,8 @@ import {
intermediaryCurrency, intermediaryCurrency,
} from 'reducers/settings' } from 'reducers/settings'
import logger from 'logger' 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( const pairsSelector = createSelector(
currenciesSelector, currenciesSelector,
@ -52,6 +54,7 @@ const addExtraPollingHooks = (schedulePoll, cancelPoll) => {
} }
} }
// TODO we should be able to pass-in our network() function
const CounterValues = createCounterValues({ const CounterValues = createCounterValues({
log: (...args) => logger.log('CounterValues:', ...args), log: (...args) => logger.log('CounterValues:', ...args),
getAPIBaseURL: () => LEDGER_COUNTERVALUES_API, getAPIBaseURL: () => LEDGER_COUNTERVALUES_API,
@ -61,4 +64,30 @@ const CounterValues = createCounterValues({
addExtraPollingHooks, 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 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 { disable as disableDBMiddleware } from 'middlewares/db'
import db from 'helpers/db' import db from 'helpers/db'
import { delay } from 'helpers/promise' 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/') const dbpath = path.resolve(resolveUserDataDirectory(), 'sqlite/')
rimraf.sync(dbpath, { glob: false }) rimraf.sync(dbpath, { glob: false })
} }

2
src/reducers/settings.js

@ -70,7 +70,7 @@ const INITIAL_STATE: SettingsState = {
currenciesSettings: {}, currenciesSettings: {},
developerMode: !!process.env.__DEV__, developerMode: !!process.env.__DEV__,
loaded: false, loaded: false,
shareAnalytics: false, shareAnalytics: true,
sentryLogs: true, sentryLogs: true,
lastUsedVersion: __APP_VERSION__, 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.", "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.", "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.", "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.", "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." "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", "title": "Oops, insufficient balance",
"description": "Make sure the account to debit has sufficient 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": { "PasswordsDontMatch": {
"title": "Passwords don't match", "title": "Passwords don't match",
"description": "Please try again" "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 data = require(`${filePath}`) // eslint-disable-line import/no-dynamic-require
const accounts = data.data.accounts.map(a => a.data) const accounts = data.data.accounts.map(a => a.data)
accounts.sort(ACCOUNT_SORT) accounts.sort(ACCOUNT_SORT)
return accounts return accounts.map(a => pick(a, ACCOUNTS_FIELDS)).map(a => {
.map(a => pick(a, ACCOUNTS_FIELDS)) a.operations.sort(OP_SORT)
.map(a => { return {
a.operations.sort(OP_SORT) ...a,
return { operations: a.operations.map(o => pick(o, OPS_FIELDS)),
...a, }
operations: a.operations.map(o => pick(o, OPS_FIELDS)), })
}
})
} }
function getOpHash(op) { function getOpHash(op) {

6
test-e2e/sync/wait-sync.js

@ -27,7 +27,11 @@ async function waitForSync() {
const areAllSync = mapped.every(account => { const areAllSync = mapped.every(account => {
const diff = now - new Date(account.lastSyncDate).getTime() const diff = now - new Date(account.lastSyncDate).getTime()
if (diff <= MIN_TIME_DIFF) return true 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 false
}) })
return areAllSync return areAllSync

6
yarn.lock

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

Loading…
Cancel
Save