Browse Source

Merge pull request #1630 from LedgerHQ/develop

Release 1.2.4
master
Gaëtan Renaudeau 6 years ago
committed by GitHub
parent
commit
cea9135722
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .eslintrc
  2. 1
      .gitignore
  3. 10
      package.json
  4. 17
      scripts/download-xliffs.sh
  5. 5
      src/actions/settings.js
  6. 9
      src/bridge/BridgeSyncContext.js
  7. 23
      src/bridge/EthereumJSBridge.js
  8. 11
      src/bridge/RippleJSBridge.js
  9. 4
      src/commands/libcoreGetFees.js
  10. 4
      src/commands/libcoreScanFromXPUB.js
  11. 14
      src/commands/libcoreSignAndBroadcast.js
  12. 4
      src/commands/libcoreSyncAccount.js
  13. 181
      src/components/CurrenciesStatusBanner.js
  14. 304
      src/components/DevToolsPage/AccountImporter.js
  15. 63
      src/components/QRCodeExporter.js
  16. 2
      src/components/SettingsPage/sections/Tools.js
  17. 2
      src/components/TopBar/index.js
  18. 1
      src/config/constants.js
  19. 6
      src/config/urls.js
  20. 2
      src/helpers/countervalues.js
  21. 44
      src/helpers/libcore.js
  22. 58
      src/reducers/currenciesStatus.js
  23. 4
      src/reducers/index.js
  24. 12
      src/reducers/settings.js
  25. 3
      static/i18n/en/app.json
  26. 87
      yarn.lock

1
.eslintrc

@ -60,6 +60,7 @@
"react/jsx-no-literals": [1, {"noStrings": false}],
"react/prefer-stateless-function": 0,
"react/require-default-props": 0,
"react/no-multi-comp": 0,
"react/sort-comp": [1, {
order: [
'static-methods',

1
.gitignore

@ -1,3 +1,4 @@
xliffs/
/.env
/dist/
/flow-typed/

10
package.json

@ -37,11 +37,11 @@
"dependencies": {
"@ledgerhq/hw-app-btc": "^4.27.0",
"@ledgerhq/hw-app-eth": "^4.24.0",
"@ledgerhq/hw-app-xrp": "^4.24.0",
"@ledgerhq/hw-app-xrp": "^4.25.0",
"@ledgerhq/hw-transport": "^4.24.0",
"@ledgerhq/hw-transport-node-hid": "4.24.0",
"@ledgerhq/ledger-core": "2.0.0-rc.9",
"@ledgerhq/live-common": "4.0.0-beta.1",
"@ledgerhq/ledger-core": "2.0.0-rc.11",
"@ledgerhq/live-common": "4.4.2",
"animated": "^0.2.2",
"async": "^2.6.1",
"axios": "^0.18.0",
@ -70,11 +70,11 @@
"measure-scrollbar": "^1.1.0",
"moment": "^2.22.2",
"qrcode": "^1.2.0",
"qrloop": "^0.6.1",
"qrloop": "0.8.1",
"qs": "^6.5.1",
"raven": "^2.5.0",
"raven-js": "^3.24.2",
"react": "^16.4.1",
"react": "^16.6.1",
"react-dom": "^16.4.1",
"react-i18next": "^7.7.0",
"react-key-handler": "^1.0.1",

17
scripts/download-xliffs.sh

@ -0,0 +1,17 @@
#!/bin/sh
if [ -z "$CROWDIN_TOKEN" ]; then
echo "CROWDIN_TOKEN env required" >&2
exit 1
fi
rm -rf xliffs
mkdir xliffs
cd xliffs
for lang in fr es-ES zh-CN ja ko ru; do
curl "https://api.crowdin.com/api/project/ledger-wallet/export-file?file=develop/static/i18n/en/app.json&language=$lang&format=xliff&key=$CROWDIN_TOKEN" > en-$lang.xliff
done
zip -r ledger-live-langs.zip *.xliff

5
src/actions/settings.js

@ -45,3 +45,8 @@ export const setExchangePairsAction: SetExchangePairs = pairs => ({
type: 'SETTINGS_SET_PAIRS',
pairs,
})
export const dismissBanner = (bannerId: string) => ({
type: 'SETTINGS_DISMISS_BANNER',
payload: bannerId,
})

9
src/bridge/BridgeSyncContext.js

@ -16,7 +16,9 @@ import { setAccountSyncState } from 'actions/bridgeSync'
import { bridgeSyncSelector, syncStateLocalSelector } from 'reducers/bridgeSync'
import type { BridgeSyncState } from 'reducers/bridgeSync'
import { accountsSelector, isUpToDateSelector } from 'reducers/accounts'
import { currenciesStatusSelector, getIsCurrencyDown } from 'reducers/currenciesStatus'
import { SYNC_MAX_CONCURRENT, SYNC_TIMEOUT } from 'config/constants'
import type { CurrencyStatus } from 'reducers/currenciesStatus'
import { getBridgeForCurrency } from '.'
type BridgeSyncProviderProps = {
@ -29,6 +31,7 @@ type BridgeSyncProviderOwnProps = BridgeSyncProviderProps & {
isUpToDate: boolean,
updateAccountWithUpdater: (string, (Account) => Account) => void,
setAccountSyncState: (string, AsyncState) => *,
currenciesStatus: CurrencyStatus[],
}
type AsyncState = {
@ -48,6 +51,7 @@ export type Sync = (action: BehaviorAction) => void
const BridgeSyncContext = React.createContext((_: BehaviorAction) => {})
const mapStateToProps = createStructuredSelector({
currenciesStatus: currenciesStatusSelector,
accounts: accountsSelector,
bridgeSync: bridgeSyncSelector,
isUpToDate: isUpToDateSelector,
@ -73,6 +77,11 @@ class Provider extends Component<BridgeSyncProviderOwnProps, Sync> {
return
}
if (getIsCurrencyDown(this.props.currenciesStatus, account.currency)) {
next()
return
}
const bridge = getBridgeForCurrency(account.currency)
this.props.setAccountSyncState(accountId, { pending: true, error: null })

23
src/bridge/EthereumJSBridge.js

@ -12,6 +12,7 @@ import {
getDerivationModesForCurrency,
getDerivationScheme,
runDerivationScheme,
isIterableDerivationMode,
getMandatoryEmptyAccountSkip,
} from '@ledgerhq/live-common/lib/derivation'
import {
@ -72,7 +73,7 @@ const txToOps = (account: Account) => (tx: Tx): Operation[] => {
const value = BigNumber(tx.value)
const fee = BigNumber(tx.gas_price * tx.gas_used)
if (sending) {
ops.push({
const op: $Exact<Operation> = {
id: `${account.id}-${tx.hash}-OUT`,
hash: tx.hash,
type: 'OUT',
@ -84,10 +85,12 @@ const txToOps = (account: Account) => (tx: Tx): Operation[] => {
senders: [tx.from],
recipients: [tx.to],
date: new Date(tx.received_at),
})
extra: {},
}
ops.push(op)
}
if (receiving) {
ops.push({
const op: $Exact<Operation> = {
id: `${account.id}-${tx.hash}-IN`,
hash: tx.hash,
type: 'IN',
@ -99,7 +102,9 @@ const txToOps = (account: Account) => (tx: Tx): Operation[] => {
senders: [tx.from],
recipients: [tx.to],
date: new Date(new Date(tx.received_at) + 1), // hack: make the IN appear after the OUT in history.
})
extra: {},
}
ops.push(op)
}
return ops
}
@ -168,7 +173,7 @@ const signAndBroadcast = async ({
const hash = await api.broadcastTransaction(transaction)
onOperationBroadcasted({
const op: $Exact<Operation> = {
id: `${a.id}-${hash}-OUT`,
hash,
type: 'OUT',
@ -181,7 +186,10 @@ const signAndBroadcast = async ({
recipients: [t.recipient],
transactionSequenceNumber: nonce,
date: new Date(),
})
extra: {},
}
onOperationBroadcasted(op)
}
}
@ -304,7 +312,8 @@ const EthereumBridge: WalletBridge<Transaction> = {
let emptyCount = 0
const mandatoryEmptyAccountSkip = getMandatoryEmptyAccountSkip(derivationMode)
const derivationScheme = getDerivationScheme({ derivationMode, currency })
for (let index = 0; index < 255; index++) {
const stopAt = isIterableDerivationMode(derivationMode) ? 255 : 1
for (let index = 0; index < stopAt; index++) {
const freshAddressPath = runDerivationScheme(derivationScheme, currency, {
account: index,
})

11
src/bridge/RippleJSBridge.js

@ -11,6 +11,7 @@ import {
getDerivationModesForCurrency,
getDerivationScheme,
runDerivationScheme,
isIterableDerivationMode,
} from '@ledgerhq/live-common/lib/derivation'
import {
getAccountPlaceholderName,
@ -104,7 +105,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera
const hash = computeBinaryTransactionHash(transaction)
onOperationBroadcasted({
const op: $Exact<Operation> = {
id: `${a.id}-${hash}-OUT`,
hash,
accountId: a.id,
@ -120,7 +121,9 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera
transactionSequenceNumber:
(a.operations.length > 0 ? a.operations[0].transactionSequenceNumber : 0) +
a.pendingOperations.length,
})
extra: {},
}
onOperationBroadcasted(op)
}
} finally {
api.disconnect()
@ -230,6 +233,7 @@ const txToOperation = (account: Account) => ({
recipients: [destination.address],
date: new Date(timestamp),
transactionSequenceNumber: sequence,
extra: {},
}
return op
}
@ -299,7 +303,8 @@ const RippleJSBridge: WalletBridge<Transaction> = {
const derivationModes = getDerivationModesForCurrency(currency)
for (const derivationMode of derivationModes) {
const derivationScheme = getDerivationScheme({ derivationMode, currency })
for (let index = 0; index < 255; index++) {
const stopAt = isIterableDerivationMode(derivationMode) ? 255 : 1
for (let index = 0; index < stopAt; index++) {
const freshAddressPath = runDerivationScheme(derivationScheme, currency, {
account: index,
})

4
src/commands/libcoreGetFees.js

@ -6,7 +6,7 @@ import withLibcore from 'helpers/withLibcore'
import { createCommand, Command } from 'helpers/ipc'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import { getWalletName } from '@ledgerhq/live-common/lib/account'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { Account, DerivationMode } from '@ledgerhq/live-common/lib/types'
import {
isValidAddress,
libcoreAmountToBigNumber,
@ -26,7 +26,7 @@ type Input = {
accountIndex: number,
transaction: BitcoinLikeTransaction,
currencyId: string,
derivationMode: string,
derivationMode: DerivationMode,
seedIdentifier: string,
}

4
src/commands/libcoreScanFromXPUB.js

@ -1,7 +1,7 @@
// @flow
import { fromPromise } from 'rxjs/observable/fromPromise'
import type { AccountRaw } from '@ledgerhq/live-common/lib/types'
import type { AccountRaw, DerivationMode } from '@ledgerhq/live-common/lib/types'
import { createCommand, Command } from 'helpers/ipc'
import withLibcore from 'helpers/withLibcore'
@ -10,7 +10,7 @@ import { scanAccountsFromXPUB } from 'helpers/libcore'
type Input = {
currencyId: string,
xpub: string,
derivationMode: string,
derivationMode: DerivationMode,
seedIdentifier: string,
}

14
src/commands/libcoreSignAndBroadcast.js

@ -7,7 +7,7 @@ import Btc from '@ledgerhq/hw-app-btc'
import { Observable } from 'rxjs'
import { isSegwitDerivationMode } from '@ledgerhq/live-common/lib/derivation'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import type { OperationRaw, CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import type { OperationRaw, DerivationMode, CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import { getWalletName } from '@ledgerhq/live-common/lib/account'
import {
libcoreAmountToBigNumber,
@ -30,7 +30,7 @@ type Input = {
accountId: string,
blockHeight: number,
currencyId: string,
derivationMode: string,
derivationMode: DerivationMode,
seedIdentifier: string,
xpub: string,
index: number,
@ -103,7 +103,7 @@ async function signTransaction({
currency: CryptoCurrency,
blockHeight: number,
transaction: *,
derivationMode: string,
derivationMode: DerivationMode,
sigHashType: number,
hasTimestamp: boolean,
}) {
@ -200,7 +200,7 @@ export async function doSignAndBroadcast({
onOperationBroadcasted,
}: {
accountId: string,
derivationMode: string,
derivationMode: DerivationMode,
seedIdentifier: string,
blockHeight: number,
currency: CryptoCurrency,
@ -280,7 +280,7 @@ export async function doSignAndBroadcast({
const fee = libcoreAmountToBigNumber(builded.getFees())
// NB we don't check isCancelled() because the broadcast is not cancellable now!
onOperationBroadcasted({
const op: $Exact<OperationRaw> = {
id: `${xpub}-${txHash}-OUT`,
hash: txHash,
type: 'OUT',
@ -294,7 +294,9 @@ export async function doSignAndBroadcast({
recipients,
accountId,
date: new Date().toISOString(),
})
extra: {},
}
onOperationBroadcasted(op)
}
export default cmd

4
src/commands/libcoreSyncAccount.js

@ -1,6 +1,6 @@
// @flow
import type { AccountRaw } from '@ledgerhq/live-common/lib/types'
import type { AccountRaw, DerivationMode } from '@ledgerhq/live-common/lib/types'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import { fromPromise } from 'rxjs/observable/fromPromise'
@ -12,7 +12,7 @@ type Input = {
accountId: string,
currencyId: string,
xpub: string,
derivationMode: string,
derivationMode: DerivationMode,
seedIdentifier: string,
index: number,
}

181
src/components/CurrenciesStatusBanner.js

@ -0,0 +1,181 @@
// @flow
import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { createStructuredSelector } from 'reselect'
import styled from 'styled-components'
import type { Currency } from '@ledgerhq/live-common/lib/types'
import { colors } from 'styles/theme'
import { openURL } from 'helpers/linking'
import { CHECK_CUR_STATUS_INTERVAL } from 'config/constants'
import IconCross from 'icons/Cross'
import IconTriangleWarning from 'icons/TriangleWarning'
import IconChevronRight from 'icons/ChevronRight'
import { dismissedBannersSelector } from 'reducers/settings'
import { currenciesStatusSelector, fetchCurrenciesStatus } from 'reducers/currenciesStatus'
import { currenciesSelector } from 'reducers/accounts'
import { dismissBanner } from 'actions/settings'
import type { CurrencyStatus } from 'reducers/currenciesStatus'
import Box from 'components/base/Box'
const mapStateToProps = createStructuredSelector({
dismissedBanners: dismissedBannersSelector,
accountsCurrencies: currenciesSelector,
currenciesStatus: currenciesStatusSelector,
})
const mapDispatchToProps = {
dismissBanner,
fetchCurrenciesStatus,
}
const getItemKey = (item: CurrencyStatus) => `${item.id}_${item.nonce}`
const CloseIconContainer = styled.div`
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
border-bottom-left-radius: 4px;
opacity: 0.5;
&:hover {
opacity: 1;
}
`
const CloseIcon = (props: *) => (
<CloseIconContainer {...props}>
<IconCross size={16} color="white" />
</CloseIconContainer>
)
type Props = {
accountsCurrencies: Currency[],
dismissedBanners: string[],
dismissBanner: string => void,
currenciesStatus: CurrencyStatus[],
fetchCurrenciesStatus: () => Promise<void>,
t: *,
}
class CurrenciesStatusBanner extends PureComponent<Props> {
componentDidMount() {
this.pollStatus()
}
componentWillUnmount() {
this.unmounted = true
if (this.timeout) {
clearTimeout(this.timeout)
}
}
unmounted = false
timeout: *
pollStatus = async () => {
await this.props.fetchCurrenciesStatus()
if (this.unmounted) return
this.timeout = setTimeout(this.pollStatus, CHECK_CUR_STATUS_INTERVAL)
}
dismiss = item => this.props.dismissBanner(getItemKey(item))
render() {
const { dismissedBanners, accountsCurrencies, currenciesStatus, t } = this.props
const filtered = currenciesStatus.filter(
item =>
accountsCurrencies.find(cur => cur.id === item.id) &&
dismissedBanners.indexOf(getItemKey(item)) === -1,
)
if (!filtered.length) return null
return (
<Box flow={2} style={styles.container}>
{filtered.map(r => <BannerItem key={r.id} t={t} item={r} onItemDismiss={this.dismiss} />)}
</Box>
)
}
}
class BannerItem extends PureComponent<{
item: CurrencyStatus,
onItemDismiss: CurrencyStatus => void,
t: *,
}> {
onLinkClick = () => openURL(this.props.item.link)
dismiss = () => this.props.onItemDismiss(this.props.item)
render() {
const { item, t } = this.props
return (
<Box relative key={item.id} style={styles.banner}>
<CloseIcon onClick={this.dismiss} />
<Box horizontal flow={2}>
<IconTriangleWarning height={16} width={16} color="white" />
<Box shrink ff="Open Sans|SemiBold">
{item.message}
</Box>
</Box>
{item.link && <BannerItemLink t={t} onClick={this.onLinkClick} />}
</Box>
)
}
}
const UnderlinedLink = styled.span`
border-bottom: 1px solid transparent;
&:hover {
border-bottom-color: white;
}
`
const BannerItemLink = ({ t, onClick }: { t: *, onClick: void => * }) => (
<Box
mt={2}
ml={4}
flow={1}
horizontal
align="center"
cursor="pointer"
onClick={onClick}
color="white"
>
<IconChevronRight size={16} color="white" />
<UnderlinedLink>{t('common.learnMore')}</UnderlinedLink>
</Box>
)
const styles = {
container: {
position: 'fixed',
left: 32,
bottom: 32,
},
banner: {
background: colors.alertRed,
overflow: 'hidden',
borderRadius: 4,
fontSize: 13,
padding: 14,
color: 'white',
fontWeight: 'bold',
paddingRight: 50,
width: 350,
},
}
export default compose(
connect(
mapStateToProps,
mapDispatchToProps,
),
translate(),
)(CurrenciesStatusBanner)

304
src/components/DevToolsPage/AccountImporter.js

@ -1,15 +1,18 @@
// @flow
/* eslint-disable react/no-multi-comp */
import React, { PureComponent, Fragment } from 'react'
import invariant from 'invariant'
import { connect } from 'react-redux'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import type { Currency, Account, DerivationMode } from '@ledgerhq/live-common/lib/types'
import { decodeAccount } from 'reducers/accounts'
import { addAccount } from 'actions/accounts'
import FormattedVal from 'components/base/FormattedVal'
import FakeLink from 'components/base/FakeLink'
import Ellipsis from 'components/base/Ellipsis'
import Switch from 'components/base/Switch'
import Spinner from 'components/base/Spinner'
import Box, { Card } from 'components/base/Box'
@ -32,26 +35,40 @@ type Props = {
addAccount: Account => void,
}
const INITIAL_STATE = {
status: 'idle',
currency: null,
xpub: '',
account: null,
isSegwit: true,
isUnsplit: false,
error: null,
type ImportableAccountType = {
name: string,
currency: Currency,
derivationMode: DerivationMode,
xpub: string,
}
type State = {
status: string,
importableAccounts: ImportableAccountType[],
currency: ?Currency,
xpub: string,
account: ?Account,
name: string,
isSegwit: boolean,
isUnsplit: boolean,
error: ?Error,
}
const INITIAL_STATE = {
status: 'idle',
currency: null,
xpub: '',
name: 'dev',
isSegwit: true,
isUnsplit: false,
error: null,
importableAccounts: [],
}
class AccountImporter extends PureComponent<Props, State> {
state = INITIAL_STATE
@ -67,142 +84,199 @@ class AccountImporter extends PureComponent<Props, State> {
onChangeXPUB = xpub => this.setState({ xpub })
onChangeSegwit = isSegwit => this.setState({ isSegwit })
onChangeUnsplit = isUnsplit => this.setState({ isUnsplit })
onChangeName = name => this.setState({ name })
isValid = () => {
const { currency, xpub } = this.state
return !!currency && !!xpub
const { currency, xpub, status } = this.state
return !!currency && !!xpub && status !== 'scanning'
}
scan = async () => {
if (!this.isValid()) return
this.setState({ status: 'scanning' })
const { importableAccounts } = this.state
try {
const { currency, xpub, isSegwit, isUnsplit } = this.state
invariant(currency, 'no currency')
const derivationMode = isSegwit
? isUnsplit
? 'segwit_unsplit'
: 'segwit'
: isUnsplit
? 'unsplit'
: ''
const rawAccount = await scanFromXPUB
.send({
seedIdentifier: 'dev_tool',
currencyId: currency.id,
xpub,
derivationMode,
for (let i = 0; i < importableAccounts.length; i++) {
const a = importableAccounts[i]
const scanPayload = {
seedIdentifier: `dev_${a.xpub}`,
currencyId: a.currency.id,
xpub: a.xpub,
derivationMode: a.derivationMode,
}
const rawAccount = await scanFromXPUB.send(scanPayload).toPromise()
const account = decodeAccount(rawAccount)
await this.import({
...account,
name: a.name,
})
.toPromise()
const account = decodeAccount(rawAccount)
this.setState({ status: 'finish', account })
this.removeImportableAccount(a)
}
this.reset()
} catch (error) {
this.setState({ status: 'error', error })
}
}
import = async () => {
const { account } = this.state
addToScan = () => {
const { xpub, currency, isSegwit, isUnsplit, name } = this.state
const derivationMode = isSegwit
? isUnsplit
? 'segwit_unsplit'
: 'segwit'
: isUnsplit
? 'unsplit'
: ''
const importableAccount = { xpub, currency, derivationMode, name }
this.setState(({ importableAccounts }) => ({
importableAccounts: [...importableAccounts, importableAccount],
currency: null,
xpub: '',
name: 'dev',
isSegwit: true,
isUnsplit: false,
}))
}
removeImportableAccount = importableAccount => {
this.setState(({ importableAccounts }) => ({
importableAccounts: importableAccounts.filter(i => i.xpub !== importableAccount.xpub),
}))
}
import = async account => {
invariant(account, 'no account')
await idleCallback()
this.props.addAccount(account)
this.reset()
}
reset = () => this.setState(INITIAL_STATE)
render() {
const { currency, xpub, isSegwit, isUnsplit, status, account, error } = this.state
const {
currency,
xpub,
name,
isSegwit,
isUnsplit,
status,
error,
importableAccounts,
} = this.state
const supportsSplit = !!currency && !!currency.forkedFrom
return (
<Card title="Import from xpub" flow={3}>
{status === 'idle' ? (
<Fragment>
<Box flow={1}>
<Label>{'currency'}</Label>
<SelectCurrency autoFocus value={currency} onChange={this.onChangeCurrency} />
</Box>
{currency && (currency.supportsSegwit || supportsSplit) ? (
<Box horizontal justify="flex-end" align="center" flow={3}>
{supportsSplit && (
<Box horizontal align="center" flow={1}>
<Box ff="Museo Sans|Bold" fontSize={4}>
{'unsplit'}
<Fragment>
<Card title="Import from xpub" flow={3}>
{status === 'idle' || status === 'scanning' ? (
<Fragment>
<Box flow={1}>
<Label>{'currency'}</Label>
<SelectCurrency autoFocus value={currency} onChange={this.onChangeCurrency} />
</Box>
{currency && (currency.supportsSegwit || supportsSplit) ? (
<Box horizontal justify="flex-end" align="center" flow={3}>
{supportsSplit && (
<Box horizontal align="center" flow={1}>
<Box ff="Museo Sans|Bold" fontSize={4}>
{'unsplit'}
</Box>
<Switch isChecked={isUnsplit} onChange={this.onChangeUnsplit} />
</Box>
<Switch isChecked={isUnsplit} onChange={this.onChangeUnsplit} />
</Box>
)}
{currency.supportsSegwit && (
<Box horizontal align="center" flow={1}>
<Box ff="Museo Sans|Bold" fontSize={4}>
{'segwit'}
)}
{currency.supportsSegwit && (
<Box horizontal align="center" flow={1}>
<Box ff="Museo Sans|Bold" fontSize={4}>
{'segwit'}
</Box>
<Switch isChecked={isSegwit} onChange={this.onChangeSegwit} />
</Box>
<Switch isChecked={isSegwit} onChange={this.onChangeSegwit} />
</Box>
)}
</Box>
) : null}
<Box flow={1}>
<Label>{'xpub'}</Label>
<Input
placeholder="xpub"
value={xpub}
onChange={this.onChangeXPUB}
onEnter={this.scan}
/>
</Box>
<Box align="flex-end">
<Button primary small disabled={!this.isValid()} onClick={this.scan}>
{'scan'}
</Button>
</Box>
</Fragment>
) : status === 'scanning' ? (
<Box align="center" justify="center" p={5}>
<Spinner size={16} />
</Box>
) : status === 'finish' ? (
account ? (
<Box p={8} align="center" justify="center" flow={5} horizontal>
<Box horizontal flow={4} color="graphite" align="center">
{currency && <CurrencyCircleIcon size={64} currency={currency} />}
<Box>
<Box ff="Museo Sans|Bold">{account.name}</Box>
<FormattedVal
fontSize={2}
alwaysShowSign={false}
color="graphite"
unit={account.unit}
showCode
val={account.balance || 0}
/>
<Box fontSize={2}>{`${account.operations.length} operation(s)`}</Box>
)}
</Box>
) : null}
<Box flow={1}>
<Label>{'xpub'}</Label>
<Input
placeholder="xpub"
value={xpub}
onChange={this.onChangeXPUB}
onEnter={this.addToScan}
/>
</Box>
<Button outline small disabled={!account} onClick={this.import}>
{'import'}
</Button>
</Box>
) : (
<Box flow={1}>
<Label>{'name'}</Label>
<Input
placeholder="name"
value={name}
onChange={this.onChangeName}
onEnter={this.addToScan}
/>
</Box>
<Box align="flex-end">
<Button primary small disabled={!this.isValid()} onClick={this.addToScan}>
{'add to scan'}
</Button>
</Box>
</Fragment>
) : status === 'error' ? (
<Box align="center" justify="center" p={5} flow={4}>
<Box>{'No accounts found or wrong xpub'}</Box>
<Box>
<TranslatedError error={error} />
</Box>
<Button primary onClick={this.reset} small autoFocus>
{'Reset'}
</Button>
</Box>
)
) : status === 'error' ? (
<Box align="center" justify="center" p={5} flow={4}>
<Box>
<TranslatedError error={error} />
</Box>
<Button primary onClick={this.reset} small autoFocus>
{'Reset'}
</Button>
</Box>
) : null}
</Card>
) : null}
</Card>
{!!importableAccounts.length && (
<Card flow={2}>
{importableAccounts.map((acc, i) => (
<ImportableAccount
key={acc.xpub}
importableAccount={acc}
onRemove={this.removeImportableAccount}
isLoading={status === 'scanning' && i === 0}
>
{acc.xpub}
</ImportableAccount>
))}
{status !== 'scanning' && (
<Box mt={4} align="flex-start">
<Button primary onClick={this.scan}>
{'Launch scan'}
</Button>
</Box>
)}
</Card>
)}
</Fragment>
)
}
}
class ImportableAccount extends PureComponent<{
importableAccount: ImportableAccountType,
onRemove: ImportableAccountType => void,
isLoading: boolean,
}> {
remove = () => {
this.props.onRemove(this.props.importableAccount)
}
render() {
const { importableAccount, isLoading } = this.props
return (
<Box horizontal flow={2} align="center">
{isLoading && <Spinner size={16} color="rgba(0, 0, 0, 0.3)" />}
<CurrencyCircleIcon currency={importableAccount.currency} size={24} />
<Box grow ff="Rubik" fontSize={3}>
<Ellipsis>{`[${importableAccount.name}] ${importableAccount.derivationMode ||
'default'} ${importableAccount.xpub}`}</Ellipsis>
</Box>
{!isLoading && (
<FakeLink onClick={this.remove} fontSize={3}>
{'Remove'}
</FakeLink>
)}
</Box>
)
}
}

63
src/components/QRCodeExporter.js

@ -2,7 +2,7 @@
import React, { PureComponent } from 'react'
import { Buffer } from 'buffer'
import { createSelector } from 'reselect'
import { createStructuredSelector } from 'reselect'
import { connect } from 'react-redux'
import { accountsSelector } from 'reducers/accounts'
@ -11,30 +11,20 @@ import { encode } from '@ledgerhq/live-common/lib/cross'
import { dataToFrames } from 'qrloop/exporter'
import QRCode from './base/QRCode'
const mapStateToProps = createSelector(
accountsSelector,
exportSettingsSelector,
(accounts, settings) => ({
chunks: dataToFrames(
encode({
accounts,
settings,
exporterName: 'desktop',
exporterVersion: __APP_VERSION__,
}),
200,
4,
),
}),
)
const mapStateToProps = createStructuredSelector({
accounts: accountsSelector,
settings: exportSettingsSelector,
})
class QRCodeExporter extends PureComponent<
{
chunks: string[],
accounts: *,
settings: *,
size: number,
},
{
frame: number,
framesRendered: number,
fps: number,
},
> {
@ -42,18 +32,35 @@ class QRCodeExporter extends PureComponent<
size: 460,
}
constructor(props) {
super()
const { accounts, settings } = props
const data = encode({
accounts,
settings,
exporterName: 'desktop',
exporterVersion: __APP_VERSION__,
})
this.chunks = dataToFrames(data, 160, 4)
setTimeout(() => {
const BRIDGESTREAM_DATA = Buffer.from(JSON.stringify(this.chunks)).toString('base64')
console.log(`BRIDGESTREAM_DATA=${BRIDGESTREAM_DATA}`) // eslint-disable-line
}, 500)
}
state = {
frame: 0,
framesRendered: 1,
fps: 3,
}
componentDidMount() {
const BRIDGESTREAM_DATA = Buffer.from(JSON.stringify(this.props.chunks)).toString('base64')
console.log(`BRIDGESTREAM_DATA=${BRIDGESTREAM_DATA}`) // eslint-disable-line
const nextFrame = ({ frame }, { chunks }) => {
frame = (frame + 1) % chunks.length
return { frame }
const nextFrame = ({ frame, framesRendered }) => {
frame = (frame + 1) % this.chunks.length
framesRendered = Math.min(Math.max(framesRendered, frame + 1), this.chunks.length)
return { frame, framesRendered }
}
let lastT
@ -71,14 +78,16 @@ class QRCodeExporter extends PureComponent<
cancelAnimationFrame(this._raf)
}
chunks: string[]
_raf: *
render() {
const { frame } = this.state
const { chunks, size } = this.props
const { frame, framesRendered } = this.state
const { size } = this.props
const { chunks } = this
return (
<div style={{ position: 'relative', width: size, height: size }}>
{chunks.map((chunk, i) => (
{chunks.slice(0, framesRendered).map((chunk, i) => (
<div key={String(i)} style={{ position: 'absolute', opacity: i === frame ? 1 : 0 }}>
<QRCode data={chunk} size={size} errorCorrectionLevel="M" />
</div>

2
src/components/SettingsPage/sections/Tools.js

@ -3,6 +3,7 @@
/* eslint-disable react/jsx-no-literals */
import React, { PureComponent } from 'react'
import liveCommonPkg from '@ledgerhq/live-common/package.json'
import { translate } from 'react-i18next'
import Box, { Card } from 'components/base/Box'
import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal'
@ -30,6 +31,7 @@ class TabProfile extends PureComponent<*, *> {
<Box flow={2}>
<QRCodeExporter />
</Box>
<Box style={{ textAlign: 'right' }}>{liveCommonPkg.version}</Box>
</ModalContent>
</ModalBody>
)

2
src/components/TopBar/index.js

@ -21,6 +21,7 @@ import IconSettings from 'icons/Settings'
import Box from 'components/base/Box'
import GlobalSearch from 'components/GlobalSearch'
import Tooltip from 'components/base/Tooltip'
import CurrenciesStatusBanner from 'components/CurrenciesStatusBanner'
import ActivityIndicator from './ActivityIndicator'
import ItemContainer from './ItemContainer'
@ -101,6 +102,7 @@ class TopBar extends PureComponent<Props> {
<Inner>
<Box grow horizontal>
<GlobalSearch t={t} isHidden />
<CurrenciesStatusBanner />
{hasAccounts && (
<Fragment>
<ActivityIndicator />

1
src/config/constants.js

@ -32,6 +32,7 @@ export const MIN_HEIGHT = intFromEnv('LEDGER_MIN_HEIGHT', 700)
export const CHECK_APP_INTERVAL_WHEN_INVALID = 600
export const CHECK_APP_INTERVAL_WHEN_VALID = 1200
export const CHECK_UPDATE_DELAY = 5000
export const CHECK_CUR_STATUS_INTERVAL = intFromEnv('CHECK_CUR_STATUS_INTERVAL', 60 * 60 * 1000)
export const DEVICE_INFOS_TIMEOUT = intFromEnv('DEVICE_INFOS_TIMEOUT', 5 * 1000)
export const GENUINE_CACHE_DELAY = intFromEnv('GENUINE_CACHE_DELAY', 1000)
export const GENUINE_TIMEOUT = intFromEnv('GENUINE_TIMEOUT', 120 * 1000)

6
src/config/urls.js

@ -8,7 +8,7 @@ export const urls = {
// Ledger support
faq: 'https://support.ledgerwallet.com/hc/en-us',
terms: 'https://www.ledgerwallet.com/terms',
terms: 'https://www.ledger.com/pages/terms-of-use-and-disclaimer',
noDeviceBuyNew: 'https://www.ledgerwallet.com/',
noDeviceTrackOrder: 'http://order.ledgerwallet.com/',
noDeviceLearnMore: 'https://www.ledgerwallet.com/',
@ -36,4 +36,8 @@ export const urls = {
errors: {
CantOpenDevice: 'https://support.ledgerwallet.com/hc/en-us/articles/115005165269',
},
// Currencies status
currenciesStatus:
'https://s3-eu-west-1.amazonaws.com/ledger-ledgerlive-resources-prod/public_resources/currencies.json',
}

2
src/helpers/countervalues.js

@ -14,6 +14,7 @@ import {
import logger from 'logger'
import { listCryptoCurrencies } from '@ledgerhq/live-common/lib/currencies'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import network from '../api/network'
const pairsSelector = createSelector(
currenciesSelector,
@ -62,6 +63,7 @@ const CounterValues = createCounterValues({
pairsSelector,
setExchangePairsAction,
addExtraPollingHooks,
network,
})
let sortCache

44
src/helpers/libcore.js

@ -11,6 +11,7 @@ import {
getDerivationScheme,
isSegwitDerivationMode,
isUnsplitDerivationMode,
isIterableDerivationMode,
} from '@ledgerhq/live-common/lib/derivation'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import {
@ -26,6 +27,7 @@ import type {
AccountRaw,
OperationRaw,
OperationType,
DerivationMode,
} from '@ledgerhq/live-common/lib/types'
import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc'
@ -89,7 +91,7 @@ async function scanAccountsOnDeviceBySegwit({
currency: CryptoCurrency,
onAccountScanned: AccountRaw => void,
isUnsubscribed: () => boolean,
derivationMode: string,
derivationMode: DerivationMode,
showNewAccount: boolean,
}): Promise<AccountRaw[]> {
const isSegwit = isSegwitDerivationMode(derivationMode)
@ -111,7 +113,6 @@ async function scanAccountsOnDeviceBySegwit({
// retrieve or create the wallet
const wallet = await getOrCreateWallet(core, walletName, { currency, derivationMode })
const accountsCount = await wallet.getAccountCount()
// recursively scan all accounts on device on the given app
// new accounts will be created in sqlite, existing ones will be updated
@ -121,7 +122,6 @@ async function scanAccountsOnDeviceBySegwit({
walletName,
devicePath,
currency,
accountsCount,
accountIndex: 0,
accounts: [],
onAccountScanned,
@ -201,8 +201,7 @@ async function scanNextAccount(props: {
devicePath: string,
currency: CryptoCurrency,
seedIdentifier: string,
derivationMode: string,
accountsCount: number,
derivationMode: DerivationMode,
accountIndex: number,
accounts: AccountRaw[],
onAccountScanned: AccountRaw => void,
@ -215,7 +214,6 @@ async function scanNextAccount(props: {
walletName,
devicePath,
currency,
accountsCount,
accountIndex,
accounts,
onAccountScanned,
@ -225,13 +223,12 @@ async function scanNextAccount(props: {
isUnsubscribed,
} = props
// create account only if account has not been scanned yet
// if it has already been created, we just need to get it, and sync it
const hasBeenScanned = accountIndex < accountsCount
const njsAccount = hasBeenScanned
? await wallet.getAccount(accountIndex)
: await createAccount(wallet, devicePath)
let njsAccount
try {
njsAccount = await wallet.getAccount(accountIndex)
} catch (err) {
njsAccount = await createAccount(wallet, devicePath)
}
if (isUnsubscribed()) return []
@ -259,15 +256,15 @@ async function scanNextAccount(props: {
if (isUnsubscribed()) return []
const isEmpty = ops.length === 0
const isLast = ops.length === 0 || !isIterableDerivationMode(derivationMode)
if (!isEmpty || showNewAccount) {
if (!isLast || showNewAccount) {
onAccountScanned(account)
accounts.push(account)
}
// returns if the current index points on an account with no ops
if (isEmpty) {
if (isLast) {
return accounts
}
@ -292,7 +289,7 @@ export async function getOrCreateWallet(
derivationMode,
}: {
currency: CryptoCurrency,
derivationMode: string,
derivationMode: DerivationMode,
},
): NJSWallet {
const pool = core.getPoolInstance()
@ -335,7 +332,7 @@ async function buildAccountRaw({
seedIdentifier: string,
walletName: string,
currency: CryptoCurrency,
derivationMode: string,
derivationMode: DerivationMode,
accountIndex: number,
core: *,
ops: NJSOperation[],
@ -428,7 +425,7 @@ function buildOperationRaw({
core: *,
op: NJSOperation,
xpub: string,
}): OperationRaw {
}): $Exact<OperationRaw> {
const bitcoinLikeOperation = op.asBitcoinLikeOperation()
const bitcoinLikeTransaction = bitcoinLikeOperation.getTransaction()
const hash = bitcoinLikeTransaction.getHash()
@ -462,6 +459,7 @@ function buildOperationRaw({
blockHash: null,
accountId: xpub, // FIXME accountId: xpub !?
date: op.getDate().toISOString(),
extra: {},
}
}
@ -475,7 +473,7 @@ export async function syncAccount({
}: {
core: *,
xpub: string,
derivationMode: string,
derivationMode: DerivationMode,
seedIdentifier: string,
currency: CryptoCurrency,
index: number,
@ -551,20 +549,18 @@ export async function scanAccountsFromXPUB({
core: *,
currencyId: string,
xpub: string,
derivationMode: string,
derivationMode: DerivationMode,
seedIdentifier: string,
}) {
const currency = getCryptoCurrencyById(currencyId)
const walletName = getWalletName({
currency,
seedIdentifier: 'debug',
seedIdentifier,
derivationMode,
})
const wallet = await getOrCreateWallet(core, walletName, { currency, derivationMode })
await wallet.eraseDataSince(new Date(0))
const index = 0
const isSegwit = isSegwitDerivationMode(derivationMode)

58
src/reducers/currenciesStatus.js

@ -0,0 +1,58 @@
// @flow
import { handleActions, createAction } from 'redux-actions'
import type { Currency } from '@ledgerhq/live-common/lib/types'
import network from 'api/network'
import { urls } from 'config/urls'
import logger from 'logger'
import type { State } from './index'
export type CurrencyStatus = {
id: string, // the currency id
status: 'KO' | 'OK',
message: string,
link: string,
nonce: number,
}
export type CurrenciesStatusState = CurrencyStatus[]
const state: CurrenciesStatusState = []
const handlers = {
CURRENCIES_STATUS_SET: (
state: CurrenciesStatusState,
{ payload }: { payload: CurrenciesStatusState },
) => payload,
}
// Actions
const setCurrenciesStatus = createAction('CURRENCIES_STATUS_SET')
export const fetchCurrenciesStatus = () => async (dispatch: *) => {
try {
const { data } = await network({
method: 'GET',
url: process.env.LL_STATUS_ENDPOINT || urls.currenciesStatus,
})
dispatch(setCurrenciesStatus(data))
} catch (err) {
logger.error(err)
}
}
// Selectors
export const currenciesStatusSelector = (state: State) => state.currenciesStatus
// It's not a *real* selector, but it's better than having this logic inside component
export const getIsCurrencyDown = (currenciesStatus: CurrenciesStatusState, currency: Currency) => {
const item = currenciesStatus.find(c => c.id === currency.id)
return !!item && item.status === 'KO'
}
// Exporting reducer
export default handleActions(handlers, state)

4
src/reducers/index.js

@ -9,6 +9,7 @@ import type { CounterValuesState } from '@ledgerhq/live-common/lib/countervalues
import CounterValues from 'helpers/countervalues'
import accounts from './accounts'
import application from './application'
import currenciesStatus from './currenciesStatus'
import devices from './devices'
import modals from './modals'
import settings from './settings'
@ -24,11 +25,13 @@ import type { SettingsState } from './settings'
import type { UpdateState } from './update'
import type { OnboardingState } from './onboarding'
import type { BridgeSyncState } from './bridgeSync'
import type { CurrenciesStatusState } from './currenciesStatus'
export type State = {
accounts: AccountsState,
application: ApplicationState,
countervalues: CounterValuesState,
currenciesStatus: CurrenciesStatusState,
devices: DevicesState,
modals: ModalsState,
router: LocationShape,
@ -42,6 +45,7 @@ export default combineReducers({
accounts,
application,
countervalues: CounterValues.reducer,
currenciesStatus,
devices,
modals,
router,

12
src/reducers/settings.js

@ -47,6 +47,7 @@ export type SettingsState = {
shareAnalytics: boolean,
sentryLogs: boolean,
lastUsedVersion: string,
dismissedBanners: string[],
}
const defaultsForCurrency: CryptoCurrency => CurrencySettings = crypto => {
@ -73,6 +74,7 @@ const INITIAL_STATE: SettingsState = {
shareAnalytics: true,
sentryLogs: true,
lastUsedVersion: __APP_VERSION__,
dismissedBanners: [],
}
function asCryptoCurrency(c: Currency): ?CryptoCurrency {
@ -126,6 +128,14 @@ const handlers: Object = {
...settings,
loaded: true,
}),
SETTINGS_DISMISS_BANNER: (state: SettingsState, { payload: bannerId }) => ({
...state,
dismissedBanners: [...state.dismissedBanners, bannerId],
}),
CLEAN_ACCOUNTS_CACHE: (state: SettingsState) => ({
...state,
dismissedBanners: [],
}),
}
// TODO refactor selectors to *Selector naming convention
@ -224,6 +234,8 @@ export const selectedTimeRangeSelector = (state: State) => state.settings.select
export const hasCompletedOnboardingSelector = (state: State) =>
state.settings.hasCompletedOnboarding
export const dismissedBannersSelector = (state: State) => state.settings.dismissedBanners || []
export const exportSettingsSelector = createSelector(
counterValueCurrencySelector,
counterValueExchangeSelector,

3
static/i18n/en/app.json

@ -11,6 +11,7 @@
"continue": "Continue",
"learnMore": "Learn more",
"help": "Help",
"dismiss": "Dismiss",
"skipThisStep": "Skip this step",
"needHelp": "Need help?",
"areYouSure": "Are you sure?",
@ -170,7 +171,7 @@
"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.",
"kyber": "KYBER, his a trading platform for exchange and conversion of ERC-20 tokens"
"kyber": "Kyber is a trading platform for exchange and conversion of ERC-20 tokens"
},
"genuinecheck": {
"modal": {

87
yarn.lock

@ -1670,7 +1670,7 @@
camelcase "^5.0.0"
prettier "^1.13.7"
"@ledgerhq/hw-app-btc@^4.27.0":
"@ledgerhq/hw-app-btc@^4.24.0", "@ledgerhq/hw-app-btc@^4.27.0":
version "4.27.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.27.0.tgz#11fc822bd34a47a39b1a7ae03ced69cf1d432796"
integrity sha512-7Ck48wCBb6nd9UXarNeGOsOqbOTi2cs4AxFhbDNrVLvPiBSH0yEiNQEF95J6u5BxKkAdM1GV9LoRumR4KhZGqQ==
@ -1701,6 +1701,14 @@
"@ledgerhq/hw-transport" "^4.24.0"
bip32-path "0.4.2"
"@ledgerhq/hw-app-xrp@^4.25.0":
version "4.25.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-4.25.0.tgz#d97d7e85290dd2d1ec99f85747a26145e7a7383e"
integrity sha512-kG9S8CxUFMG1hbLBiKtoPMquzlTigndDsxhoXB8oywAdbGsoCi2cufMHV2p5Bek6YlGfn5J5p49Hx5iNIf2y5Q==
dependencies:
"@ledgerhq/hw-transport" "^4.24.0"
bip32-path "0.4.2"
"@ledgerhq/hw-transport-node-hid@4.24.0":
version "4.24.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.24.0.tgz#8457969d66819e8f7f50d5dd96527ab26cd3787d"
@ -1733,10 +1741,10 @@
dependencies:
events "^3.0.0"
"@ledgerhq/ledger-core@2.0.0-rc.9":
version "2.0.0-rc.9"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.9.tgz#f478f52fac0ecace8842df430bc83fa60e14483f"
integrity sha512-wd+w4W/8IfhX2a4h1sM9rkKD0vfcixkbrvKa+SmKzv0WtiUx1i94rHlPLX08tbP2ubaAv/LufeT2DwFz+EcssA==
"@ledgerhq/ledger-core@2.0.0-rc.11":
version "2.0.0-rc.11"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.11.tgz#5b314e222f487dfa8f525ba1ef008ae30b289339"
integrity sha512-HmtUd3WrVhJQtjNe6qO/hGrnzrE2YbdaTQnLhsQyD3qN1vUwHmanHjqOqVLFRI8a3KqVdMFqYMvn3N5c0hsLuQ==
dependencies:
"@ledgerhq/hw-app-btc" "^4.7.3"
"@ledgerhq/hw-transport-node-hid" "^4.7.6"
@ -1745,19 +1753,23 @@
bindings "^1.3.0"
nan "^2.6.2"
"@ledgerhq/live-common@4.0.0-beta.1":
version "4.0.0-beta.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-4.0.0-beta.1.tgz#52ed90757761a08a5f5c9c40c1fed0cb2f1fb4ca"
integrity sha512-Ms06Za/EI8yP/4GOAziCMecqIssZbkS5tjSCgaqd1h0zHLzM6i6PvgjhJyvG5SrET6+uQVp4YM4qbo2tc6irjQ==
"@ledgerhq/live-common@4.4.2":
version "4.4.2"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-4.4.2.tgz#0c60efe9f13fa17c39db880ee87d68561ba54da8"
integrity sha512-oghcqyZY9G2xSH/PDDqTH2DZ+JLeRLEN0nEEo1t48zdXsu6LZ5ZweF8pK3jSETi+IfLYOzUuB93iJM1+AcVYAA==
dependencies:
axios "^0.18.0"
"@ledgerhq/hw-app-btc" "^4.24.0"
"@ledgerhq/hw-app-eth" "^4.24.0"
"@ledgerhq/hw-app-xrp" "^4.24.0"
"@ledgerhq/hw-transport" "^4.24.0"
bignumber.js "^7.2.1"
compressjs gre/compressjs#hermit
eip55 "^1.0.3"
invariant "^2.2.2"
lodash "^4.17.4"
node-lzw "^0.3.1"
numeral "^2.0.6"
prando "^3.0.1"
react "^16.4.0"
react "*"
react-i18next "^8.0.7"
react-redux "^5.0.7"
redux "^4.0.0"
@ -5224,6 +5236,13 @@ commander@~2.13.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==
commander@~2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
dependencies:
graceful-readlink ">= 1.0.0"
common-tags@^1.7.2:
version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@ -5279,6 +5298,12 @@ compression@^1.5.2:
safe-buffer "5.1.1"
vary "~1.1.2"
compressjs@gre/compressjs#hermit:
version "1.2.0"
resolved "https://codeload.github.com/gre/compressjs/tar.gz/19652f100671e559d7731980c26cd06a7e999b8d"
dependencies:
commander "~2.8.1"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -8461,6 +8486,11 @@ graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6,
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
"graceful-readlink@>= 1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
grouped-queue@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-0.3.3.tgz#c167d2a5319c5a0e0964ef6a25b7c2df8996c85c"
@ -11393,11 +11423,6 @@ node-loader@^0.6.0:
resolved "https://registry.yarnpkg.com/node-loader/-/node-loader-0.6.0.tgz#c797ef51095ed5859902b157f6384f6361e05ae8"
integrity sha1-x5fvUQle1YWZArFX9jhPY2HgWug=
node-lzw@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/node-lzw/-/node-lzw-0.3.1.tgz#f50e37968976aca83320028b91f101df4a436b2d"
integrity sha1-9Q43lol2rKgzIAKLkfEB30pDay0=
node-modules-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
@ -12861,10 +12886,10 @@ qrcode@^1.2.0:
pngjs "^3.3.0"
yargs "^8.0.2"
qrloop@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/qrloop/-/qrloop-0.6.1.tgz#167f8868f03018c7625b0e887049830e9253beae"
integrity sha512-1wJwoKpukmkfqwzyb9lMMEE9BDWXlKv63J/gdWMIeUfzgSR3QrZ3VKTtjN9Xvyo1DWwJd/G3FuMtJaH9cf+XUw==
qrloop@0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/qrloop/-/qrloop-0.8.1.tgz#27fa3dabfba4247842fdcc7a1e5a439c69ba3e2c"
integrity sha512-8erD63Y39HFY1x4OsaA3ZZdcUB1FA8vz0rkQSH95ECiRnjPtk+NOz+bw7dFHQBiVqZX/6I2GyczSNTnjUV+17w==
dependencies:
flow-typed "^2.5.1"
md5 "^2.2.1"
@ -13342,15 +13367,15 @@ react-treebeard@^2.1.0:
shallowequal "^0.2.2"
velocity-react "^1.3.1"
react@^16.2.0, react@^16.4.0, react@^16.4.1:
version "16.4.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32"
integrity sha512-3GEs0giKp6E0Oh/Y9ZC60CmYgUPnp7voH9fbjWsvXtYFb4EWtgQub0ADSq0sJR0BbHc4FThLLtzlcFaFXIorwg==
react@*, react@^16.2.0, react@^16.6.1:
version "16.6.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.6.1.tgz#ee2aef4f0a09e494594882029821049772f915fe"
integrity sha512-OtawJThYlvRgm9BXK+xTL7BIlDx8vv21j+fbQDjRRUyok6y7NyjlweGorielTahLZHYIdKUoK2Dp9ByVWuMqxw==
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"
prop-types "^15.6.2"
scheduler "^0.11.0"
reactcss@^1.2.0:
version "1.2.3"
@ -14249,6 +14274,14 @@ sax@^1.2.4, sax@~1.2.1, sax@~1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
scheduler@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.0.tgz#def1f1bfa6550cc57981a87106e65e8aea41a6b5"
integrity sha512-MAYbBfmiEHxF0W+c4CxMpEqMYK+rYF584VP/qMKSiHM6lTkBKKYOJaDiSILpJHla6hBOsVd6GucPL46o2Uq3sg==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"

Loading…
Cancel
Save