Browse Source

Porting to new live-common with changes in Account/Operation

master
Gaëtan Renaudeau 7 years ago
parent
commit
acf0db75b3
  1. 2
      package.json
  2. 51
      src/bridge/EthereumJSBridge.js
  3. 54
      src/bridge/RippleJSBridge.js
  4. 9
      src/bridge/makeMockBridge.js
  5. 2
      src/components/CurrentAddress/stories.js
  6. 9
      src/components/CurrentAddressForAccount.js
  7. 12
      src/components/DeviceCheckAddress.js
  8. 8
      src/components/EnsureDeviceApp/index.js
  9. 12
      src/components/OperationsList/ConfirmationCheck.js
  10. 33
      src/components/OperationsList/Operation.js
  11. 11
      src/components/modals/OperationDetails.js
  12. 2
      src/components/modals/Send/04-step-confirmation.js
  13. 48
      src/internals/accounts/scanAccountsOnDevice.js
  14. 2
      src/internals/accounts/signAndBroadcastTransaction/btc.js
  15. 4
      static/i18n/en/operationsList.yml
  16. 4
      static/i18n/fr/operationsList.yml
  17. 6
      yarn.lock

2
package.json

@ -42,7 +42,7 @@
"@ledgerhq/hw-transport": "^4.12.0", "@ledgerhq/hw-transport": "^4.12.0",
"@ledgerhq/hw-transport-node-hid": "^4.12.0", "@ledgerhq/hw-transport-node-hid": "^4.12.0",
"@ledgerhq/ledger-core": "^1.2.0", "@ledgerhq/ledger-core": "^1.2.0",
"@ledgerhq/live-common": "^2.7.5", "@ledgerhq/live-common": "2.8.0-beta.2",
"axios": "^0.18.0", "axios": "^0.18.0",
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",

51
src/bridge/EthereumJSBridge.js

@ -24,15 +24,17 @@ const EditFees = ({ account, onChange, value }: EditProps<Transaction>) => (
/> />
) )
const toAccountOperation = (account: Account) => (tx: Tx): Operation => { const toAccountOperation = (account: Account) => (tx: Tx): $Exact<Operation> => {
const sending = account.address.toLowerCase() === tx.from.toLowerCase() const sending = account.freshAddress.toLowerCase() === tx.from.toLowerCase()
const receiving = account.freshAddress.toLowerCase() === tx.to.toLowerCase()
const type = sending && receiving ? 'SELF' : sending ? 'OUT' : 'IN'
return { return {
id: tx.hash, id: tx.hash,
hash: tx.hash, hash: tx.hash,
address: sending ? tx.to : tx.from, type,
amount: (sending ? -1 : 1) * tx.value, value: tx.value,
blockHeight: (tx.block && tx.block.height) || 0, // FIXME will be optional field blockHeight: tx.block && tx.block.height,
blockHash: (tx.block && tx.block.hash) || '', // FIXME will be optional field blockHash: tx.block && tx.block.hash,
accountId: account.id, accountId: account.id,
senders: [tx.from], senders: [tx.from],
recipients: [tx.to], recipients: [tx.to],
@ -56,7 +58,7 @@ const paginateMoreTransactions = async (
): Promise<Operation[]> => { ): Promise<Operation[]> => {
const api = apiForCurrency(account.currency) const api = apiForCurrency(account.currency)
const { txs } = await api.getTransactions( const { txs } = await api.getTransactions(
account.address, account.freshAddress,
acc.length ? acc[acc.length - 1].blockHash : undefined, acc.length ? acc[acc.length - 1].blockHash : undefined,
) )
if (txs.length === 0) return acc if (txs.length === 0) return acc
@ -93,7 +95,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
async function stepAddress( async function stepAddress(
index, index,
{ address, path }, { address, path: freshAddressPath },
isStandard, isStandard,
): { account?: Account, complete?: boolean } { ): { account?: Account, complete?: boolean } {
const balance = await api.getAccountBalance(address) const balance = await api.getAccountBalance(address)
@ -103,6 +105,9 @@ const EthereumBridge: WalletBridge<Transaction> = {
const { txs } = await api.getTransactions(address) const { txs } = await api.getTransactions(address)
if (finished) return { complete: true } if (finished) return { complete: true }
const path = freshAddressPath // FIXME
const freshAddress = address
if (txs.length === 0) { if (txs.length === 0) {
// this is an empty account // this is an empty account
if (isStandard) { if (isStandard) {
@ -110,21 +115,20 @@ const EthereumBridge: WalletBridge<Transaction> = {
// first zero account will emit one account as opportunity to create a new account.. // first zero account will emit one account as opportunity to create a new account..
const currentBlock = await fetchCurrentBlock(currency) const currentBlock = await fetchCurrentBlock(currency)
const accountId = `${currency.id}_${address}` const accountId = `${currency.id}_${address}`
const account: Account = { const account: $Exact<Account> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
path, // FIXME we probably not want the address path in the account.path path, // FIXME we probably not want the address path in the account.path
walletPath: String(index), freshAddress,
freshAddressPath,
name: 'New Account', name: 'New Account',
isSegwit: false,
address,
addresses: [{ str: address, path }],
balance, balance,
blockHeight: currentBlock.height, blockHeight: currentBlock.height,
archived: true, archived: true,
index, index,
currency, currency,
operations: [], operations: [],
pendingOperations: [],
unit: currency.units[0], unit: currency.units[0],
lastSyncDate: new Date(), lastSyncDate: new Date(),
} }
@ -137,21 +141,20 @@ const EthereumBridge: WalletBridge<Transaction> = {
} }
const accountId = `${currency.id}_${address}` const accountId = `${currency.id}_${address}`
const account: Account = { const account: $Exact<Account> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
path, // FIXME we probably not want the address path in the account.path path, // FIXME we probably not want the address path in the account.path
walletPath: String(index), freshAddress,
freshAddressPath,
name: address.slice(32), name: address.slice(32),
isSegwit: false,
address,
addresses: [{ str: address, path }],
balance, balance,
blockHeight: currentBlock.height, blockHeight: currentBlock.height,
archived: true, archived: true,
index, index,
currency, currency,
operations: [], operations: [],
pendingOperations: [],
unit: currency.units[0], unit: currency.units[0],
lastSyncDate: new Date(), lastSyncDate: new Date(),
} }
@ -166,9 +169,9 @@ const EthereumBridge: WalletBridge<Transaction> = {
for (const derivation of derivations) { for (const derivation of derivations) {
const isStandard = last === derivation const isStandard = last === derivation
for (let index = 0; index < 255; index++) { for (let index = 0; index < 255; index++) {
const path = derivation({ currency, x: index, segwit: false }) const freshAddressPath = derivation({ currency, x: index, segwit: false })
const res = await getAddressCommand const res = await getAddressCommand
.send({ currencyId: currency.id, devicePath: deviceId, path }) .send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath })
.toPromise() .toPromise()
const r = await stepAddress(index, res, isStandard) const r = await stepAddress(index, res, isStandard)
if (r.account) next(r.account) if (r.account) next(r.account)
@ -188,7 +191,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
return { unsubscribe } return { unsubscribe }
}, },
synchronize({ address, blockHeight, currency }, { next, complete, error }) { synchronize({ freshAddress, blockHeight, currency }, { next, complete, error }) {
let unsubscribed = false let unsubscribed = false
const api = apiForCurrency(currency) const api = apiForCurrency(currency)
async function main() { async function main() {
@ -198,9 +201,9 @@ const EthereumBridge: WalletBridge<Transaction> = {
if (block.height === blockHeight) { if (block.height === blockHeight) {
complete() complete()
} else { } else {
const balance = await api.getAccountBalance(address) const balance = await api.getAccountBalance(freshAddress)
if (unsubscribed) return if (unsubscribed) return
const { txs } = await api.getTransactions(address) const { txs } = await api.getTransactions(freshAddress)
if (unsubscribed) return if (unsubscribed) return
next(a => { next(a => {
const currentOps = a.operations const currentOps = a.operations
@ -277,7 +280,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
signAndBroadcast: async (a, t, deviceId) => { signAndBroadcast: async (a, t, deviceId) => {
const api = apiForCurrency(a.currency) const api = apiForCurrency(a.currency)
const nonce = await api.getAccountNonce(a.address) const nonce = await api.getAccountNonce(a.freshAddress)
const transaction = await signTransactionCommand const transaction = await signTransactionCommand
.send({ .send({

54
src/bridge/RippleJSBridge.js

@ -122,19 +122,18 @@ const txToOperation = (account: Account) => ({
outcome: { deliveredAmount, ledgerVersion, timestamp }, outcome: { deliveredAmount, ledgerVersion, timestamp },
specification: { source, destination }, specification: { source, destination },
}: Tx): Operation => { }: Tx): Operation => {
const sending = source.address === account.address const type = source.address === account.freshAddress ? 'OUT' : 'IN'
const amount = const value = deliveredAmount ? parseAPICurrencyObject(deliveredAmount) : 0
(sending ? -1 : 1) * (deliveredAmount ? parseAPICurrencyObject(deliveredAmount) : 0) const op: $Exact<Operation> = {
const op: Operation = {
id, id,
hash: id, hash: id,
accountId: account.id, accountId: account.id,
blockHash: '', type,
address: sending ? destination.address : source.address, value,
amount, blockHash: null,
blockHeight: ledgerVersion, blockHeight: ledgerVersion,
senders: [sending ? destination.address : source.address], senders: [source.address],
recipients: [!sending ? destination.address : source.address], recipients: [destination.address],
date: new Date(timestamp), date: new Date(timestamp),
} }
return op return op
@ -159,9 +158,11 @@ const RippleJSBridge: WalletBridge<Transaction> = {
const derivations = getDerivations(currency) const derivations = getDerivations(currency)
for (const derivation of derivations) { for (const derivation of derivations) {
for (let index = 0; index < 255; index++) { for (let index = 0; index < 255; index++) {
const path = derivation({ currency, x: index, segwit: false }) const freshAddressPath = derivation({ currency, x: index, segwit: false })
const path = freshAddressPath
// FIXME^ we need the account path, not the address path
const { address } = await await getAddress const { address } = await await getAddress
.send({ currencyId: currency.id, devicePath: deviceId, path }) .send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath })
.toPromise() .toPromise()
if (finished) return if (finished) return
@ -176,6 +177,9 @@ const RippleJSBridge: WalletBridge<Transaction> = {
} }
} }
// fresh address is address. ripple never changes.
const freshAddress = address
if (!info) { if (!info) {
// account does not exist in Ripple server // account does not exist in Ripple server
// we are generating a new account locally // we are generating a new account locally
@ -183,16 +187,15 @@ const RippleJSBridge: WalletBridge<Transaction> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
path, path,
walletPath: '',
name: 'New Account', name: 'New Account',
isSegwit: false, freshAddress,
address, freshAddressPath,
addresses: [address],
balance: 0, balance: 0,
blockHeight: maxLedgerVersion, blockHeight: maxLedgerVersion,
index, index,
currency, currency,
operations: [], operations: [],
pendingOperations: [],
unit: currency.units[0], unit: currency.units[0],
archived: true, archived: true,
lastSyncDate: new Date(), lastSyncDate: new Date(),
@ -212,20 +215,19 @@ const RippleJSBridge: WalletBridge<Transaction> = {
}) })
if (finished) return if (finished) return
const account: Account = { const account: $Exact<Account> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
path, path,
walletPath: '',
name: address.slice(0, 8), name: address.slice(0, 8),
isSegwit: false, freshAddress,
address, freshAddressPath,
addresses: [address],
balance, balance,
blockHeight: maxLedgerVersion, blockHeight: maxLedgerVersion,
index, index,
currency, currency,
operations: [], operations: [],
pendingOperations: [],
unit: currency.units[0], unit: currency.units[0],
archived: true, archived: true,
lastSyncDate: new Date(), lastSyncDate: new Date(),
@ -247,7 +249,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
return { unsubscribe } return { unsubscribe }
}, },
synchronize({ currency, address, blockHeight }, { next, error, complete }) { synchronize({ currency, freshAddress, blockHeight }, { next, error, complete }) {
let finished = false let finished = false
const unsubscribe = () => { const unsubscribe = () => {
finished = true finished = true
@ -266,7 +268,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
let info let info
try { try {
info = await api.getAccountInfo(address) info = await api.getAccountInfo(freshAddress)
} catch (e) { } catch (e) {
if (e.message !== 'actNotFound') { if (e.message !== 'actNotFound') {
throw e throw e
@ -282,12 +284,12 @@ const RippleJSBridge: WalletBridge<Transaction> = {
const balance = parseAPIValue(info.xrpBalance) const balance = parseAPIValue(info.xrpBalance)
if (isNaN(balance) || !isFinite(balance)) { if (isNaN(balance) || !isFinite(balance)) {
throw new Error(`Ripple: invalid balance=${balance} for address ${address}`) throw new Error(`Ripple: invalid balance=${balance} for address ${freshAddress}`)
} }
next(a => ({ ...a, balance })) next(a => ({ ...a, balance }))
const transactions = await api.getTransactions(address, { const transactions = await api.getTransactions(freshAddress, {
minLedgerVersion: Math.max(blockHeight, minLedgerVersion), minLedgerVersion: Math.max(blockHeight, minLedgerVersion),
maxLedgerVersion, maxLedgerVersion,
}) })
@ -362,7 +364,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
const amount = formatAPICurrencyXRP(t.amount) const amount = formatAPICurrencyXRP(t.amount)
const payment = { const payment = {
source: { source: {
address: a.address, address: a.freshAddress,
amount, amount,
}, },
destination: { destination: {
@ -375,7 +377,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
fee: formatAPICurrencyXRP(t.fee).value, fee: formatAPICurrencyXRP(t.fee).value,
} }
const prepared = await api.preparePayment(a.address, payment, instruction) const prepared = await api.preparePayment(a.freshAddress, payment, instruction)
const transaction = await signTransaction const transaction = await signTransaction
.send({ .send({

9
src/bridge/makeMockBridge.js

@ -4,6 +4,7 @@ import {
genAddingOperationsInAccount, genAddingOperationsInAccount,
genOperation, genOperation,
} from '@ledgerhq/live-common/lib/mock/account' } from '@ledgerhq/live-common/lib/mock/account'
import { getOperationAmountNumber } from '@ledgerhq/live-common/lib/helpers/operation'
import Prando from 'prando' import Prando from 'prando'
import type { Operation } from '@ledgerhq/live-common/lib/types' import type { Operation } from '@ledgerhq/live-common/lib/types'
import type { WalletBridge } from './types' import type { WalletBridge } from './types'
@ -53,7 +54,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
account = { ...account } account = { ...account }
account.blockHeight++ account.blockHeight++
for (const op of ops) { for (const op of ops) {
account.balance += op.amount account.balance += getOperationAmountNumber(op)
} }
return account return account
}) })
@ -149,8 +150,10 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
signAndBroadcast: async (account, t) => { signAndBroadcast: async (account, t) => {
const rng = new Prando() const rng = new Prando()
const op = genOperation(account, account.operations, account.currency, rng) const op = genOperation(account, account.operations, account.currency, rng)
op.amount = -t.amount op.type = 'OUT'
op.address = t.recipient op.value = t.amount
op.senders = [account.freshAddress]
op.recipients = [t.recipient]
op.blockHeight = account.blockHeight op.blockHeight = account.blockHeight
op.date = new Date() op.date = new Date()
broadcasted[account.id] = (broadcasted[account.id] || []).concat(op) broadcasted[account.id] = (broadcasted[account.id] || []).concat(op)

2
src/components/CurrentAddress/stories.js

@ -13,7 +13,7 @@ const stories = storiesOf('Components', module)
stories.add('CurrentAddress', () => ( stories.add('CurrentAddress', () => (
<CurrentAddress <CurrentAddress
accountName={text('accountName', '')} accountName={text('accountName', '')}
address={accounts[0].address} address={accounts[0].freshAddress}
addressVerified={boolean('addressVerified', true)} addressVerified={boolean('addressVerified', true)}
withBadge={boolean('withBadge', false)} withBadge={boolean('withBadge', false)}
withFooter={boolean('withFooter', false)} withFooter={boolean('withFooter', false)}

9
src/components/CurrentAddressForAccount.js

@ -11,12 +11,5 @@ type Props = {
export default function CurrentAddressForAccount(props: Props) { export default function CurrentAddressForAccount(props: Props) {
const { account, ...p } = props const { account, ...p } = props
return <CurrentAddress accountName={account.name} address={account.freshAddress} {...p} />
// TODO: handle other cryptos than BTC-like
let freshAddress = account.addresses[0]
if (!freshAddress) {
freshAddress = { str: '', path: '' }
}
return <CurrentAddress accountName={account.name} address={freshAddress.str} {...p} />
} }

12
src/components/DeviceCheckAddress.js

@ -42,23 +42,17 @@ class CheckAddress extends PureComponent<Props, State> {
verifyAddress = async ({ device, account }: { device: Device, account: Account }) => { verifyAddress = async ({ device, account }: { device: Device, account: Account }) => {
try { try {
// TODO: this will work only for BTC-like accounts
const freshAddress = account.addresses[0]
if (!freshAddress) {
throw new Error('Account doesnt have fresh addresses')
}
const { address } = await getAddress const { address } = await getAddress
.send({ .send({
currencyId: account.currency.id, currencyId: account.currency.id,
devicePath: device.path, devicePath: device.path,
path: freshAddress.path, path: account.freshAddressPath,
segwit: account.isSegwit, segwit: !!account.isSegwit,
verify: true, verify: true,
}) })
.toPromise() .toPromise()
if (address !== freshAddress.str) { if (address !== account.freshAddress) {
throw new Error('Confirmed address is different') throw new Error('Confirmed address is different')
} }

8
src/components/EnsureDeviceApp/index.js

@ -102,9 +102,9 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
options = { options = {
devicePath: deviceSelected.path, devicePath: deviceSelected.path,
currencyId: account.currency.id, currencyId: account.currency.id,
path: account.path, path: account.freshAddressPath,
accountAddress: account.address, accountAddress: account.freshAddress,
segwit: account.path.startsWith("49'"), // TODO: store segwit info in account segwit: !!account.isSegwit,
} }
} else if (currency) { } else if (currency) {
options = { options = {
@ -118,7 +118,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
try { try {
const { address } = await getAddress.send(options).toPromise() const { address } = await getAddress.send(options).toPromise()
if (account && account.address !== address) { if (account && account.freshAddress !== address) {
throw new Error('Account address is different than device address') throw new Error('Account address is different than device address')
} }
this.handleStatusChange(this.state.deviceStatus, 'success') this.handleStatusChange(this.state.deviceStatus, 'success')

12
src/components/OperationsList/ConfirmationCheck.js

@ -3,6 +3,8 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import type { OperationType } from '@ledgerhq/live-common/lib/types'
import { rgba } from 'styles/helpers' import { rgba } from 'styles/helpers'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -16,14 +18,14 @@ import Tooltip from 'components/base/Tooltip'
const Container = styled(Box).attrs({ const Container = styled(Box).attrs({
bg: p => bg: p =>
p.isConfirmed ? rgba(p.type === 'from' ? p.marketColor : p.theme.colors.grey, 0.2) : 'none', p.isConfirmed ? rgba(p.type === 'IN' ? p.marketColor : p.theme.colors.grey, 0.2) : 'none',
color: p => (p.type === 'from' ? p.marketColor : p.theme.colors.grey), color: p => (p.type === 'IN' ? p.marketColor : p.theme.colors.grey),
align: 'center', align: 'center',
justify: 'center', justify: 'center',
})` })`
border: ${p => border: ${p =>
!p.isConfirmed !p.isConfirmed
? `1px solid ${p.type === 'from' ? p.marketColor : rgba(p.theme.colors.grey, 0.2)}` ? `1px solid ${p.type === 'IN' ? p.marketColor : rgba(p.theme.colors.grey, 0.2)}`
: 0}; : 0};
border-radius: 50%; border-radius: 50%;
position: relative; position: relative;
@ -55,14 +57,14 @@ const ConfirmationCheck = ({
confirmations: number, confirmations: number,
minConfirmations: number, minConfirmations: number,
t: T, t: T,
type: 'to' | 'from', type: OperationType,
withTooltip?: boolean, withTooltip?: boolean,
}) => { }) => {
const isConfirmed = confirmations >= minConfirmations const isConfirmed = confirmations >= minConfirmations
const renderContent = () => ( const renderContent = () => (
<Container type={type} isConfirmed={isConfirmed} marketColor={marketColor} {...props}> <Container type={type} isConfirmed={isConfirmed} marketColor={marketColor} {...props}>
{type === 'from' ? <IconReceive size={12} /> : <IconSend size={12} />} {type === 'IN' ? <IconReceive size={12} /> : <IconSend size={12} />}
{!isConfirmed && ( {!isConfirmed && (
<WrapperClock> <WrapperClock>
<IconClock size={10} /> <IconClock size={10} />

33
src/components/OperationsList/Operation.js

@ -7,8 +7,9 @@ import { createStructuredSelector } from 'reselect'
import moment from 'moment' import moment from 'moment'
import noop from 'lodash/noop' import noop from 'lodash/noop'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react' import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import { getOperationAmountNumber } from '@ledgerhq/live-common/lib/helpers/operation'
import type { Account, Operation as OperationType } from '@ledgerhq/live-common/lib/types' import type { Account, Operation } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -103,15 +104,15 @@ const Cell = styled(Box).attrs({
type Props = { type Props = {
account: Account, account: Account,
currencySettings: *, currencySettings: *,
onAccountClick: Function, onAccountClick: (account: Account) => void,
onOperationClick: Function, onOperationClick: ({ operation: Operation, account: Account, marketColor: string }) => void,
marketIndicator: string, marketIndicator: string,
t: T, t: T,
op: OperationType, op: Operation, // FIXME rename it operation
withAccount: boolean, withAccount: boolean,
} }
class Operation extends PureComponent<Props> { class OperationComponent extends PureComponent<Props> {
static defaultProps = { static defaultProps = {
onAccountClick: noop, onAccountClick: noop,
onOperationClick: noop, onOperationClick: noop,
@ -132,8 +133,8 @@ class Operation extends PureComponent<Props> {
const { unit, currency } = account const { unit, currency } = account
const time = moment(op.date) const time = moment(op.date)
const Icon = getCryptoCurrencyIcon(account.currency) const Icon = getCryptoCurrencyIcon(account.currency)
const isNegative = op.amount < 0 const amount = getOperationAmountNumber(op)
const type = !isNegative ? 'from' : 'to' const isNegative = amount < 0
const marketColor = getMarketColor({ const marketColor = getMarketColor({
marketIndicator, marketIndicator,
@ -141,12 +142,12 @@ class Operation extends PureComponent<Props> {
}) })
return ( return (
<OperationRaw onClick={() => onOperationClick({ operation: op, account, type, marketColor })}> <OperationRaw onClick={() => onOperationClick({ operation: op, account, marketColor })}>
<Cell size={CONFIRMATION_COL_SIZE} align="center" justify="flex-start"> <Cell size={CONFIRMATION_COL_SIZE} align="center" justify="flex-start">
<ConfirmationCheck <ConfirmationCheck
type={type} type={op.type}
minConfirmations={currencySettings.minConfirmations} minConfirmations={currencySettings.minConfirmations}
confirmations={account.blockHeight - op.blockHeight} confirmations={op.blockHeight ? account.blockHeight - op.blockHeight : 0}
marketColor={marketColor} marketColor={marketColor}
t={t} t={t}
/> />
@ -154,7 +155,7 @@ class Operation extends PureComponent<Props> {
<Cell size={DATE_COL_SIZE} justifyContent="space-between" px={3}> <Cell size={DATE_COL_SIZE} justifyContent="space-between" px={3}>
<Box> <Box>
<Box ff="Open Sans|SemiBold" fontSize={3} color="smoke"> <Box ff="Open Sans|SemiBold" fontSize={3} color="smoke">
{t(`operationsList:${type}`)} {t(`operationsList:${op.type}`)}
</Box> </Box>
<Hour>{time.format('HH:mm')}</Hour> <Hour>{time.format('HH:mm')}</Hour>
</Box> </Box>
@ -185,24 +186,24 @@ class Operation extends PureComponent<Props> {
</Cell> </Cell>
)} )}
<Cell grow shrink style={{ display: 'block' }}> <Cell grow shrink style={{ display: 'block' }}>
<Address value={op.address} /> <Address value={op.type === 'IN' ? op.senders[0] : op.recipients[0]} />
</Cell> </Cell>
<Cell size={AMOUNT_COL_SIZE} justify="flex-end"> <Cell size={AMOUNT_COL_SIZE} justify="flex-end">
<Box alignItems="flex-end"> <Box alignItems="flex-end">
<FormattedVal <FormattedVal
val={op.amount} val={amount}
unit={unit} unit={unit}
showCode showCode
fontSize={4} fontSize={4}
alwaysShowSign alwaysShowSign
color={op.amount < 0 ? 'smoke' : undefined} color={amount < 0 ? 'smoke' : undefined}
/> />
<CounterValue <CounterValue
color="grey" color="grey"
fontSize={3} fontSize={3}
date={time.toDate()} date={time.toDate()}
currency={currency} currency={currency}
value={op.amount} value={amount}
exchange={currencySettings.exchange} exchange={currencySettings.exchange}
/> />
</Box> </Box>
@ -212,4 +213,4 @@ class Operation extends PureComponent<Props> {
} }
} }
export default connect(mapStateToProps)(Operation) export default connect(mapStateToProps)(OperationComponent)

11
src/components/modals/OperationDetails.js

@ -6,6 +6,7 @@ import { shell } from 'electron'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import moment from 'moment' import moment from 'moment'
import { getOperationAmountNumber } from '@ledgerhq/live-common/lib/helpers/operation'
import type { Account, Operation } from '@ledgerhq/live-common/lib/types' import type { Account, Operation } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -61,18 +62,18 @@ type Props = {
t: T, t: T,
operation: Operation, operation: Operation,
account: Account, account: Account,
type: 'from' | 'to', onClose: () => void,
onClose: Function,
currencySettings: *, currencySettings: *,
marketColor: string, marketColor: string,
} }
const OperationDetails = connect(mapStateToProps)((props: Props) => { const OperationDetails = connect(mapStateToProps)((props: Props) => {
const { t, type, onClose, operation, account, marketColor, currencySettings } = props const { t, onClose, operation, account, marketColor, currencySettings } = props
const { id, hash, amount, date, senders, recipients } = operation const { id, hash, date, senders, recipients, type } = operation
const amount = getOperationAmountNumber(operation)
const { name, unit, currency } = account const { name, unit, currency } = account
const confirmations = account.blockHeight - operation.blockHeight const confirmations = operation.blockHeight ? account.blockHeight - operation.blockHeight : 0
const isConfirmed = confirmations >= currencySettings.minConfirmations const isConfirmed = confirmations >= currencySettings.minConfirmations
return ( return (
<ModalBody onClose={onClose}> <ModalBody onClose={onClose}>

2
src/components/modals/Send/04-step-confirmation.js

@ -53,7 +53,7 @@ function StepConfirmation(props: Props) {
</span> </span>
<Title>{t(`${tPrefix}.title`)}</Title> <Title>{t(`${tPrefix}.title`)}</Title>
<Text>{multiline(t(`${tPrefix}.text`))}</Text> <Text>{multiline(t(`${tPrefix}.text`))}</Text>
<Text>{txValidated || ''}</Text> <Text style={{ userSelect: 'text' }}>{txValidated || ''}</Text>
</Container> </Container>
) )
} }

48
src/internals/accounts/scanAccountsOnDevice.js

@ -14,7 +14,7 @@ import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currenc
import type Transport from '@ledgerhq/hw-transport' import type Transport from '@ledgerhq/hw-transport'
import type { AccountRaw } from '@ledgerhq/live-common/lib/types' import type { AccountRaw, OperationRaw, OperationType } from '@ledgerhq/live-common/lib/types'
import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc' import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc'
type Props = { type Props = {
@ -211,6 +211,7 @@ async function buildAccountRaw({
// $FlowFixMe // $FlowFixMe
ops: NJSOperation[], ops: NJSOperation[],
}): Promise<AccountRaw> { }): Promise<AccountRaw> {
/*
const balanceByDay = ops.length const balanceByDay = ops.length
? await getBalanceByDaySinceOperation({ ? await getBalanceByDaySinceOperation({
njsAccount, njsAccount,
@ -218,6 +219,7 @@ async function buildAccountRaw({
core, core,
}) })
: {} : {}
*/
const njsBalance = await njsAccount.getBalance() const njsBalance = await njsAccount.getBalance()
const balance = njsBalance.toLong() const balance = njsBalance.toLong()
@ -244,23 +246,28 @@ async function buildAccountRaw({
path: `${accountPath}/${njsAddress.getDerivationPath()}`, path: `${accountPath}/${njsAddress.getDerivationPath()}`,
})) }))
if (addresses.length === 0) {
throw new Error('no addresses found')
}
const { str: freshAddress, path: freshAddressPath } = addresses[0]
const operations = ops.map(op => buildOperationRaw({ core, op, xpub })) const operations = ops.map(op => buildOperationRaw({ core, op, xpub }))
const rawAccount: AccountRaw = { const rawAccount: AccountRaw = {
id: xpub, id: xpub, // FIXME for account id you might want to prepend the crypto currency id to this because it's not gonna be unique.
xpub, xpub,
path: accountPath, path: walletPath,
walletPath,
name: `Account ${accountIndex}${isSegwit ? ' (segwit)' : ''}`, // TODO: placeholder name? name: `Account ${accountIndex}${isSegwit ? ' (segwit)' : ''}`, // TODO: placeholder name?
isSegwit, isSegwit,
address: bitcoinAddress, freshAddress,
addresses, freshAddressPath,
balance, balance,
blockHeight, blockHeight,
archived: false, archived: false,
index: accountIndex, index: accountIndex,
balanceByDay,
operations, operations,
pendingOperations: [],
currencyId, currencyId,
unitMagnitude: jsCurrency.units[0].magnitude, unitMagnitude: jsCurrency.units[0].magnitude,
lastSyncDate: new Date().toISOString(), lastSyncDate: new Date().toISOString(),
@ -269,31 +276,45 @@ async function buildAccountRaw({
return rawAccount return rawAccount
} }
function buildOperationRaw({ core, op, xpub }: { core: Object, op: NJSOperation, xpub: string }) { function buildOperationRaw({
core,
op,
xpub,
}: {
core: Object,
op: NJSOperation,
xpub: string,
}): OperationRaw {
const id = op.getUid() const id = op.getUid()
const bitcoinLikeOperation = op.asBitcoinLikeOperation() const bitcoinLikeOperation = op.asBitcoinLikeOperation()
const bitcoinLikeTransaction = bitcoinLikeOperation.getTransaction() const bitcoinLikeTransaction = bitcoinLikeOperation.getTransaction()
const hash = bitcoinLikeTransaction.getHash() const hash = bitcoinLikeTransaction.getHash()
const operationType = op.getOperationType() const operationType = op.getOperationType()
const absoluteAmount = op.getAmount().toLong() const value = op.getAmount().toLong()
const OperationTypeMap: { [_: $Keys<typeof core.OPERATION_TYPES>]: OperationType } = {
[core.OPERATION_TYPES.SEND]: 'OUT',
[core.OPERATION_TYPES.RECEIVE]: 'IN',
}
// if transaction is a send, amount becomes negative // if transaction is a send, amount becomes negative
const amount = operationType === core.OPERATION_TYPES.SEND ? -absoluteAmount : absoluteAmount const type = OperationTypeMap[operationType]
return { return {
id, id,
hash, hash,
address: '', type,
value,
senders: op.getSenders(), senders: op.getSenders(),
recipients: op.getRecipients(), recipients: op.getRecipients(),
blockHeight: op.getBlockHeight(), blockHeight: op.getBlockHeight(),
blockHash: '', blockHash: null,
accountId: xpub, accountId: xpub,
date: op.getDate().toISOString(), date: op.getDate().toISOString(),
amount,
} }
} }
/*
async function getBalanceByDaySinceOperation({ async function getBalanceByDaySinceOperation({
njsAccount, njsAccount,
njsOperation, njsOperation,
@ -336,3 +357,4 @@ function areSameDay(date1: Date, date2: Date): boolean {
date1.getDate() === date2.getDate() date1.getDate() === date2.getDate()
) )
} }
*/

2
src/internals/accounts/signAndBroadcastTransaction/btc.js

@ -37,7 +37,7 @@ export default async function signAndBroadcastTransactionBTCLike(
const WALLET_IDENTIFIER = await getWalletIdentifier({ const WALLET_IDENTIFIER = await getWalletIdentifier({
hwApp, hwApp,
isSegwit: account.isSegwit, isSegwit: !!account.isSegwit,
currencyId: account.currencyId, currencyId: account.currencyId,
devicePath: deviceId, devicePath: deviceId,
}) })

4
static/i18n/en/operationsList.yml

@ -2,8 +2,8 @@ date: Date
account: Account account: Account
address: Address address: Address
amount: Amount amount: Amount
from: Receive funds IN: Receive funds
to: Sent funds OUT: Sent funds
showMore: Show more showMore: Show more
confirmed: Confirmed confirmed: Confirmed
notConfirmed: Not confirmed notConfirmed: Not confirmed

4
static/i18n/fr/operationsList.yml

@ -3,8 +3,8 @@ date: Date
account: Compte account: Compte
address: Adresse address: Adresse
amount: Montant amount: Montant
from: Fonds reçus IN: Fonds reçus
to: Fonds envoyés OUT: Fonds envoyés
showMore: Voir plus showMore: Voir plus
confirmed: Confirmée confirmed: Confirmée
notConfirmed: Non confirmée notConfirmed: Non confirmée

6
yarn.lock

@ -1480,9 +1480,9 @@
npm "^5.7.1" npm "^5.7.1"
prebuild-install "^2.2.2" prebuild-install "^2.2.2"
"@ledgerhq/live-common@^2.7.5": "@ledgerhq/live-common@2.8.0-beta.2":
version "2.7.5" version "2.8.0-beta.2"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.7.5.tgz#5434bf2e708aaca471be4ca823e613cf27ba700c" resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.8.0-beta.2.tgz#942c457ee01add58752698fd525f45abdcf4597b"
dependencies: dependencies:
axios "^0.18.0" axios "^0.18.0"
invariant "^2.2.2" invariant "^2.2.2"

Loading…
Cancel
Save