Browse Source

Merge branch 'master' into onboarding

master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
43cd3d9d30
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      package.json
  2. 2
      src/api/Ethereum.js
  3. 48
      src/api/Ripple.js
  4. 64
      src/bridge/EthereumJSBridge.js
  5. 2
      src/bridge/LibcoreBridge.js
  6. 402
      src/bridge/RippleJSBridge.js
  7. 4
      src/bridge/index.js
  8. 2
      src/bridge/makeMockBridge.js
  9. 6
      src/bridge/types.js
  10. 36
      src/components/AdvancedOptions/RippleKind.js
  11. 3
      src/components/DeviceSignTransaction.js
  12. 2
      src/components/FeesField/GenericContainer.js
  13. 59
      src/components/FeesField/RippleKind.js
  14. 5
      src/components/modals/Send/03-step-verification.js
  15. 3
      src/helpers/getAddressForCurrency/index.js
  16. 15
      src/helpers/getAddressForCurrency/ripple.js
  17. 4
      src/helpers/signTransactionForCurrency/index.js
  18. 14
      src/helpers/signTransactionForCurrency/ripple.js
  19. 3
      static/i18n/en/send.yml
  20. 162
      yarn.lock

5
package.json

@ -38,6 +38,7 @@
"dependencies": {
"@ledgerhq/hw-app-btc": "^4.12.0",
"@ledgerhq/hw-app-eth": "^4.12.0",
"@ledgerhq/hw-app-xrp": "^4.12.0",
"@ledgerhq/hw-transport": "^4.12.0",
"@ledgerhq/hw-transport-node-hid": "^4.12.0",
"@ledgerhq/ledger-core": "^1.2.0",
@ -83,6 +84,10 @@
"redux-actions": "^2.3.0",
"redux-thunk": "^2.2.0",
"reselect": "^3.0.1",
"ripple-binary-codec": "^0.1.13",
"ripple-bs58check": "^2.0.2",
"ripple-hashes": "^0.3.1",
"ripple-lib": "^1.0.0-beta.0",
"rxjs": "^6.2.0",
"rxjs-compat": "^6.1.0",
"smooth-scrollbar": "^8.2.7",

2
src/api/Ethereum.js

@ -17,7 +17,7 @@ export type Tx = {
to: string,
input: string,
index: number,
block: {
block?: {
hash: string,
height: number,
time: string,

48
src/api/Ripple.js

@ -0,0 +1,48 @@
// @flow
import { RippleAPI } from 'ripple-lib'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import {
parseCurrencyUnit,
getCryptoCurrencyById,
formatCurrencyUnit,
} from '@ledgerhq/live-common/lib/helpers/currencies'
const rippleUnit = getCryptoCurrencyById('ripple').units[0]
const apiEndpoint = {
ripple: 'wss://s1.ripple.com',
}
export const apiForCurrency = (currency: CryptoCurrency) => {
const api = new RippleAPI({
server: apiEndpoint[currency.id],
})
api.on('error', (errorCode, errorMessage) => {
console.warn(`Ripple API error: ${errorCode}: ${errorMessage}`)
})
return api
}
export const parseAPIValue = (value: string) => parseCurrencyUnit(rippleUnit, value)
export const parseAPICurrencyObject = ({
currency,
value,
}: {
currency: string,
value: string,
}) => {
if (currency !== 'XRP') {
console.warn(`RippleJS: attempt to parse unknown currency ${currency}`)
return 0
}
return parseAPIValue(value)
}
export const formatAPICurrencyXRP = (amount: number) => {
const value = formatCurrencyUnit(rippleUnit, amount, {
showAllDigits: true,
disableRounding: true,
})
return { currency: 'XRP', value }
}

64
src/bridge/EthereumJSBridge.js

@ -1,6 +1,7 @@
// @flow
import React from 'react'
import EthereumKind from 'components/FeesField/EthereumKind'
import throttle from 'lodash/throttle'
import type { Account, Operation } from '@ledgerhq/live-common/lib/types'
import { apiForCurrency } from 'api/Ethereum'
import type { Tx } from 'api/Ethereum'
@ -30,8 +31,8 @@ const toAccountOperation = (account: Account) => (tx: Tx): Operation => {
hash: tx.hash,
address: sending ? tx.to : tx.from,
amount: (sending ? -1 : 1) * tx.value,
blockHeight: tx.block && tx.block.height,
blockHash: tx.block && tx.block.hash,
blockHeight: (tx.block && tx.block.height) || 0, // FIXME will be optional field
blockHash: (tx.block && tx.block.hash) || '', // FIXME will be optional field
accountId: account.id,
senders: [tx.from],
recipients: [tx.to],
@ -62,6 +63,21 @@ const paginateMoreTransactions = async (
return mergeOps(acc, txs.map(toAccountOperation(account)))
}
const fetchCurrentBlock = (perCurrencyId => currency => {
if (perCurrencyId[currency.id]) return perCurrencyId[currency.id]
const api = apiForCurrency(currency)
const f = throttle(
() =>
api.getCurrentBlock().catch(e => {
f.cancel()
throw e
}),
5000,
)
perCurrencyId[currency.id] = f
return f
})({})
const EthereumBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) {
let finished = false
@ -73,15 +89,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
// in future ideally what we want is:
// return mergeMap(addressesObservable, address => fetchAccount(address))
let balanceZerosCount = 0
let currentBlockPromise
function lazyCurrentBlock() {
if (!currentBlockPromise) {
currentBlockPromise = api.getCurrentBlock()
}
return currentBlockPromise
}
let newAccountCount = 0
async function stepAddress(
index,
@ -89,12 +97,18 @@ const EthereumBridge: WalletBridge<Transaction> = {
isStandard,
): { account?: Account, complete?: boolean } {
const balance = await api.getAccountBalance(address)
if (finished) return {}
if (balance === 0) {
if (finished) return { complete: true }
const currentBlock = await fetchCurrentBlock(currency)
if (finished) return { complete: true }
const { txs } = await api.getTransactions(address)
if (finished) return { complete: true }
if (txs.length === 0) {
// this is an empty account
if (isStandard) {
if (balanceZerosCount === 0) {
if (newAccountCount === 0) {
// first zero account will emit one account as opportunity to create a new account..
const currentBlock = await lazyCurrentBlock()
const currentBlock = await fetchCurrentBlock(currency)
const accountId = `${currency.id}_${address}`
const account: Account = {
id: accountId,
@ -104,7 +118,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
name: 'New Account',
isSegwit: false,
address,
addresses: [{ str: address, path, }],
addresses: [{ str: address, path }],
balance,
blockHeight: currentBlock.height,
archived: true,
@ -116,26 +130,22 @@ const EthereumBridge: WalletBridge<Transaction> = {
}
return { account, complete: true }
}
balanceZerosCount++
newAccountCount++
}
// NB for legacy addresses we might not want to stop at first zero but continue forever
// NB for legacy addresses maybe we will continue at least for the first 10 addresses
return { complete: true }
}
const currentBlock = await lazyCurrentBlock()
if (finished) return {}
const { txs } = await api.getTransactions(address)
if (finished) return {}
const accountId = `${currency.id}_${address}`
const account: Account = {
id: accountId,
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),
name: address.slice(32),
isSegwit: false,
address,
addresses: [{ str: address, path, }],
addresses: [{ str: address, path }],
balance,
blockHeight: currentBlock.height,
archived: true,
@ -157,12 +167,10 @@ const EthereumBridge: WalletBridge<Transaction> = {
const isStandard = last === derivation
for (let index = 0; index < 255; index++) {
const path = derivation({ currency, x: index, segwit: false })
console.log(path)
const res = await getAddressCommand
.send({ currencyId: currency.id, devicePath: deviceId, path })
.toPromise()
const r = await stepAddress(index, res, isStandard)
console.log('=>', r.account)
if (r.account) next(r.account)
if (r.complete) {
break
@ -185,7 +193,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
const api = apiForCurrency(currency)
async function main() {
try {
const block = await api.getCurrentBlock()
const block = await fetchCurrentBlock(currency)
if (unsubscribed) return
if (block.height === blockHeight) {
complete()
@ -266,7 +274,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
getMaxAmount: (a, t) => Promise.resolve(a.balance - t.gasPrice),
signAndBroadcast: async ({ account: a, transaction: t, deviceId }) => {
signAndBroadcast: async (a, t, deviceId) => {
const api = apiForCurrency(a.currency)
const nonce = await api.getAccountNonce(a.address)

2
src/bridge/LibcoreBridge.js

@ -154,7 +154,7 @@ const LibcoreBridge: WalletBridge<Transaction> = {
getMaxAmount: (a, t) => Promise.resolve(a.balance - t.feePerByte),
signAndBroadcast: ({ account, transaction, deviceId }) => {
signAndBroadcast: (account, transaction, deviceId) => {
const rawAccount = encodeAccount(account)
return runJob({
channel: 'accounts',

402
src/bridge/RippleJSBridge.js

@ -0,0 +1,402 @@
// @flow
import React from 'react'
import bs58check from 'ripple-bs58check'
import { computeBinaryTransactionHash } from 'ripple-hashes'
import type { Account, Operation } from '@ledgerhq/live-common/lib/types'
import { getDerivations } from 'helpers/derivations'
import getAddress from 'commands/getAddress'
import signTransaction from 'commands/signTransaction'
import {
apiForCurrency,
parseAPIValue,
parseAPICurrencyObject,
formatAPICurrencyXRP,
} from 'api/Ripple'
import FeesRippleKind from 'components/FeesField/RippleKind'
import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind'
import type { WalletBridge, EditProps } from './types'
type Transaction = {
amount: number,
recipient: string,
fee: number,
tag: ?number,
}
const EditFees = ({ account, onChange, value }: EditProps<Transaction>) => (
<FeesRippleKind
onChange={fee => {
onChange({ ...value, fee })
}}
fee={value.fee}
account={account}
/>
)
const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
<AdvancedOptionsRippleKind
tag={value.tag}
onChangeTag={tag => {
onChange({ ...value, tag })
}}
/>
)
function isRecipientValid(currency, recipient) {
try {
bs58check.decode(recipient)
return true
} catch (e) {
return false
}
}
function mergeOps(existing: Operation[], newFetched: Operation[]) {
const ids = existing.map(o => o.id)
const all = existing.concat(newFetched.filter(o => !ids.includes(o.id)))
return all.sort((a, b) => a.date - b.date)
}
type Tx = {
type: string,
address: string,
sequence: number,
id: string,
specification: {
source: {
address: string,
maxAmount: {
currency: string,
value: string,
},
},
destination: {
address: string,
amount: {
currency: string,
value: string,
},
},
paths: string,
},
outcome: {
result: string,
fee: string,
timestamp: string,
deliveredAmount?: {
currency: string,
value: string,
counterparty: string,
},
balanceChanges: {
[addr: string]: Array<{
counterparty: string,
currency: string,
value: string,
}>,
},
orderbookChanges: {
[addr: string]: Array<{
direction: string,
quantity: {
currency: string,
value: string,
},
totalPrice: {
currency: string,
counterparty: string,
value: string,
},
makeExchangeRate: string,
sequence: number,
status: string,
}>,
},
ledgerVersion: number,
indexInLedger: number,
},
}
const txToOperation = (account: Account) => ({
id,
outcome: { deliveredAmount, ledgerVersion, timestamp },
specification: { source, destination },
}: Tx): Operation => {
const sending = source.address === account.address
const amount =
(sending ? -1 : 1) * (deliveredAmount ? parseAPICurrencyObject(deliveredAmount) : 0)
const op: Operation = {
id,
hash: id,
accountId: account.id,
blockHash: '',
address: sending ? destination.address : source.address,
amount,
blockHeight: ledgerVersion,
senders: [sending ? destination.address : source.address],
recipients: [!sending ? destination.address : source.address],
date: new Date(timestamp),
}
return op
}
const RippleJSBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) {
let finished = false
const unsubscribe = () => {
finished = true
}
async function main() {
const api = apiForCurrency(currency)
try {
await api.connect()
const serverInfo = await api.getServerInfo()
const ledgers = serverInfo.completeLedgers.split('-')
const minLedgerVersion = Number(ledgers[0])
const maxLedgerVersion = Number(ledgers[1])
const derivations = getDerivations(currency)
for (const derivation of derivations) {
for (let index = 0; index < 255; index++) {
const path = derivation({ currency, x: index, segwit: false })
const { address } = await await getAddress
.send({ currencyId: currency.id, devicePath: deviceId, path })
.toPromise()
if (finished) return
const accountId = `${currency.id}_${address}`
let info
try {
info = await api.getAccountInfo(address)
} catch (e) {
if (e.message !== 'actNotFound') {
throw e
}
}
if (!info) {
// account does not exist in Ripple server
// we are generating a new account locally
next({
id: accountId,
xpub: '',
path,
walletPath: '',
name: 'New Account',
isSegwit: false,
address,
addresses: [address],
balance: 0,
blockHeight: maxLedgerVersion,
index,
currency,
operations: [],
unit: currency.units[0],
archived: true,
lastSyncDate: new Date(),
})
break
}
if (finished) return
const balance = parseAPIValue(info.xrpBalance)
if (isNaN(balance) || !isFinite(balance)) {
throw new Error(`Ripple: invalid balance=${balance} for address ${address}`)
}
const transactions = await api.getTransactions(address, {
minLedgerVersion,
maxLedgerVersion,
})
if (finished) return
const account: Account = {
id: accountId,
xpub: '',
path,
walletPath: '',
name: address.slice(0, 8),
isSegwit: false,
address,
addresses: [address],
balance,
blockHeight: maxLedgerVersion,
index,
currency,
operations: [],
unit: currency.units[0],
archived: true,
lastSyncDate: new Date(),
}
account.operations = transactions.map(txToOperation(account))
next(account)
}
}
complete()
} catch (e) {
error(e)
} finally {
api.disconnect()
}
}
main()
return { unsubscribe }
},
synchronize({ currency, address, blockHeight }, { next, error, complete }) {
let finished = false
const unsubscribe = () => {
finished = true
}
async function main() {
const api = apiForCurrency(currency)
try {
await api.connect()
if (finished) return
const serverInfo = await api.getServerInfo()
if (finished) return
const ledgers = serverInfo.completeLedgers.split('-')
const minLedgerVersion = Number(ledgers[0])
const maxLedgerVersion = Number(ledgers[1])
let info
try {
info = await api.getAccountInfo(address)
} catch (e) {
if (e.message !== 'actNotFound') {
throw e
}
}
if (finished) return
if (!info) {
// account does not exist, we have nothing to sync
complete()
return
}
const balance = parseAPIValue(info.xrpBalance)
if (isNaN(balance) || !isFinite(balance)) {
throw new Error(`Ripple: invalid balance=${balance} for address ${address}`)
}
next(a => ({ ...a, balance }))
const transactions = await api.getTransactions(address, {
minLedgerVersion: Math.max(blockHeight, minLedgerVersion),
maxLedgerVersion,
})
if (finished) return
next(a => {
const newOps = transactions.map(txToOperation(a))
const operations = mergeOps(a.operations, newOps)
return {
...a,
operations,
blockHeight: maxLedgerVersion,
lastSyncDate: new Date(),
}
})
complete()
} catch (e) {
error(e)
} finally {
api.disconnect()
}
}
main()
return { unsubscribe }
},
pullMoreOperations: () => Promise.resolve(a => a), // FIXME not implemented
isRecipientValid: (currency, recipient) => Promise.resolve(isRecipientValid(currency, recipient)),
createTransaction: () => ({
amount: 0,
recipient: '',
fee: 0,
tag: undefined,
}),
editTransactionAmount: (account, t, amount) => ({
...t,
amount,
}),
getTransactionAmount: (a, t) => t.amount,
editTransactionRecipient: (account, t, recipient) => ({
...t,
recipient,
}),
// $FlowFixMe
EditFees,
// $FlowFixMe
EditAdvancedOptions,
getTransactionRecipient: (a, t) => t.recipient,
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,
getTotalSpent: (a, t) => Promise.resolve(t.amount + t.fee),
getMaxAmount: (a, t) => Promise.resolve(a.balance - t.fee),
signAndBroadcast: async (a, t, deviceId) => {
const api = apiForCurrency(a.currency)
try {
await api.connect()
const amount = formatAPICurrencyXRP(t.amount)
const payment = {
source: {
address: a.address,
amount,
},
destination: {
address: t.recipient,
minAmount: amount,
tag: t.tag,
},
}
const instruction = {
fee: formatAPICurrencyXRP(t.fee).value,
}
const prepared = await api.preparePayment(a.address, payment, instruction)
const transaction = await signTransaction
.send({
currencyId: a.currency.id,
devicePath: deviceId,
path: a.path,
transaction: JSON.parse(prepared.txJSON),
})
.toPromise()
const submittedPayment = await api.submit(transaction)
if (submittedPayment.resultCode !== 'tesSUCCESS') {
throw new Error(submittedPayment.resultMessage)
}
return computeBinaryTransactionHash(transaction)
} finally {
api.disconnect()
}
},
}
export default RippleJSBridge

4
src/bridge/index.js

@ -1,11 +1,9 @@
// @flow
import type { Currency } from '@ledgerhq/live-common/lib/types'
import { WalletBridge } from './types'
import UnsupportedBridge from './UnsupportedBridge'
import LibcoreBridge from './LibcoreBridge'
import EthereumJSBridge from './EthereumJSBridge'
const RippleJSBridge = UnsupportedBridge
import RippleJSBridge from './RippleJSBridge'
export const getBridgeForCurrency = (currency: Currency): WalletBridge<any> => {
if (currency.id.indexOf('ethereum') === 0) {

2
src/bridge/makeMockBridge.js

@ -146,7 +146,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
getMaxAmount,
signAndBroadcast: async ({ account, transaction: t }) => {
signAndBroadcast: async (account, t) => {
const rng = new Prando()
const op = genOperation(account, account.operations, account.currency, rng)
op.amount = -t.amount

6
src/bridge/types.js

@ -98,9 +98,5 @@ export interface WalletBridge<Transaction> {
* NOTE: in future, when transaction balance is close to account.balance, we could wipe it all at this level...
* to implement that, we might want to have special logic `account.balance-transaction.amount < dust` but not sure where this should leave (i would say on UI side because we need to inform user visually).
*/
signAndBroadcast({
account: Account,
transaction: Transaction,
deviceId: DeviceId,
}): Promise<string>;
signAndBroadcast(account: Account, transaction: Transaction, deviceId: DeviceId): Promise<string>;
}

36
src/components/AdvancedOptions/RippleKind.js

@ -0,0 +1,36 @@
// @flow
import React from 'react'
import { translate } from 'react-i18next'
import Box from 'components/base/Box'
import Input from 'components/base/Input'
import Label from 'components/base/Label'
import Spoiler from 'components/base/Spoiler'
type Props = {
tag: ?number,
onChangeTag: (?number) => void,
t: *,
}
export default translate()(({ tag, onChangeTag, t }: Props) => (
<Spoiler title="Advanced options">
<Box horizontal align="center" flow={5}>
<Box style={{ width: 200 }}>
<Label>
<span>{t('send:steps.amount.rippleTag')}</span>
</Label>
</Box>
<Box grow>
<Input
value={String(tag || '')}
onChange={str => {
const tag = parseInt(str, 10)
if (!isNaN(tag) && isFinite(tag)) onChangeTag(tag)
else onChangeTag(undefined)
}}
/>
</Box>
</Box>
</Spoiler>
))

3
src/components/DeviceSignTransaction.js

@ -34,9 +34,10 @@ class DeviceSignTransaction extends PureComponent<Props, State> {
sign = async () => {
const { device, account, transaction, bridge, onSuccess } = this.props
try {
const txid = await bridge.signAndBroadcast({ account, transaction, deviceId: device.path })
const txid = await bridge.signAndBroadcast(account, transaction, device.path)
onSuccess(txid)
} catch (error) {
console.warn(error)
this.setState({ error })
}
}

2
src/components/FeesField/GenericContainer.js

@ -12,7 +12,7 @@ export default translate()(
<Box flow={1}>
<Label>
<span>{t('send:steps.amount.fees')}</span>
<LabelInfoTooltip ml={1} text={help} />
{help ? <LabelInfoTooltip ml={1} text={help} /> : null}
</Label>
<Box horizontal flow={5}>
{children}

59
src/components/FeesField/RippleKind.js

@ -0,0 +1,59 @@
// @flow
import React, { Component } from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types'
import { apiForCurrency, parseAPIValue } from 'api/Ripple'
import InputCurrency from 'components/base/InputCurrency'
import GenericContainer from './GenericContainer'
type Props = {
account: Account,
fee: number,
onChange: number => void,
}
type State = {
error: ?Error,
}
class FeesField extends Component<Props, State> {
state = {
error: null,
}
componentDidMount() {
this.sync()
}
async sync() {
const api = apiForCurrency(this.props.account.currency)
try {
await api.connect()
const info = await api.getServerInfo()
const serverFee = parseAPIValue(info.validatedLedger.baseFeeXRP)
if (!this.props.fee) {
this.props.onChange(serverFee)
}
} catch (error) {
this.setState({ error })
} finally {
api.disconnect()
}
}
render() {
const { account, fee, onChange } = this.props
const { error } = this.state
const { units } = account.currency
return (
<GenericContainer error={error}>
<InputCurrency
defaultUnit={units[0]}
units={units}
containerProps={{ grow: true }}
value={fee}
onChange={onChange}
/>
</GenericContainer>
)
}
}
export default FeesField

5
src/components/modals/Send/03-step-verification.js

@ -52,7 +52,10 @@ export default ({ account, device, bridge, transaction, onValidate, t }: Props)
transaction={transaction}
bridge={bridge}
onSuccess={onValidate}
render={({ error }) => <DeviceConfirm notValid={!!error} />}
render={({ error }) => (
// FIXME we really really REALLY should use error for the display. otherwise we are completely blind on error cases..
<DeviceConfirm notValid={!!error} />
)}
/>
)}
</Container>

3
src/helpers/getAddressForCurrency/index.js

@ -3,6 +3,7 @@
import type Transport from '@ledgerhq/hw-transport'
import btc from './btc'
import ethereum from './ethereum'
import ripple from './ripple'
type Resolver = (
transport: Transport<*>,
@ -24,6 +25,8 @@ const all = {
ethereum_testnet: ethereum,
ethereum_classic: ethereum,
ethereum_classic_testnet: ethereum,
ripple,
}
const getAddressForCurrency: Module = (currencyId: string) =>

15
src/helpers/getAddressForCurrency/ripple.js

@ -0,0 +1,15 @@
// @flow
import Xrp from '@ledgerhq/hw-app-xrp'
import type Transport from '@ledgerhq/hw-transport'
export default async (
transport: Transport<*>,
currencyId: string,
path: string,
{ verify = false }: { verify: boolean },
) => {
const xrp = new Xrp(transport)
const { address, publicKey } = await xrp.getAddress(path, verify)
return { path, address, publicKey }
}

4
src/helpers/signTransactionForCurrency/index.js

@ -1,7 +1,9 @@
// @flow
import type Transport from '@ledgerhq/hw-transport'
import ethereum from './ethereum'
import ripple from './ripple'
type Resolver = (
transport: Transport<*>,
@ -20,6 +22,8 @@ const all = {
ethereum_testnet: ethereum,
ethereum_classic: ethereum,
ethereum_classic_testnet: ethereum,
ripple,
}
const m: Module = (currencyId: string) => all[currencyId] || fallback(currencyId)

14
src/helpers/signTransactionForCurrency/ripple.js

@ -0,0 +1,14 @@
// @flow
import Xrp from '@ledgerhq/hw-app-xrp'
import type Transport from '@ledgerhq/hw-transport'
import BinaryCodec from 'ripple-binary-codec'
export default async (transport: Transport<*>, currencyId: string, path: string, tx: Object) => {
tx = { ...tx }
const xrp = new Xrp(transport)
const { publicKey } = await xrp.getAddress(path)
tx.SigningPubKey = publicKey.toUpperCase()
const rawTxHex = BinaryCodec.encode(tx).toUpperCase()
tx.TxnSignature = (await xrp.signTransaction(path, rawTxHex)).toUpperCase()
return BinaryCodec.encode(tx).toUpperCase()
}

3
static/i18n/en/send.yml

@ -11,6 +11,7 @@ steps:
advancedOptions: Advanced options
useRBF: Use the RBF transaction
message: Leave a message (140)
rippleTag: Tag
connectDevice:
title: Connect device
verification:
@ -23,7 +24,7 @@ steps:
title: Confirmation
success:
title: Transaction successfully completed
text: You may have to wait few confirmations unitl the transaction appear
text: You may have to wait few confirmations unitl the transaction appear
cta: View operation details
error:
title: Transaction aborted

162
yarn.lock

@ -1447,6 +1447,13 @@
dependencies:
"@ledgerhq/hw-transport" "^4.12.0"
"@ledgerhq/hw-app-xrp@^4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-4.12.0.tgz#aeba3b5b2447e185811974312a3ed1b3eeb7a0f1"
dependencies:
"@ledgerhq/hw-transport" "^4.12.0"
bip32-path "0.4.2"
"@ledgerhq/hw-transport-node-hid@^4.12.0", "@ledgerhq/hw-transport-node-hid@^4.7.6":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.12.0.tgz#acd913904d0e600c681375c7bf5e0f9d5165a075"
@ -1729,6 +1736,14 @@
react-split-pane "^0.1.77"
react-treebeard "^2.1.0"
"@types/lodash@^4.14.85":
version "4.14.109"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.109.tgz#b1c4442239730bf35cabaf493c772b18c045886d"
"@types/node@*":
version "10.1.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6"
"@types/node@^8.0.24":
version "8.10.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3"
@ -1737,6 +1752,12 @@
version "1.13.6"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.6.tgz#128d1685a7c34d31ed17010fc87d6a12c1de6976"
"@types/ws@^3.2.0":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-3.2.1.tgz#b0c1579e58e686f83ce0a97bb9463d29705827fb"
dependencies:
"@types/node" "*"
"@webassemblyjs/ast@1.4.3":
version "1.4.3"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.4.3.tgz#3b3f6fced944d8660273347533e6d4d315b5934a"
@ -3279,13 +3300,19 @@ babel-register@^6.26.0, babel-register@^6.9.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.5.0, babel-runtime@^6.9.2:
babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.5.0, babel-runtime@^6.6.1, babel-runtime@^6.9.2:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
babel-runtime@^5.8.20:
version "5.8.38"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19"
dependencies:
core-js "^1.0.0"
babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
@ -3347,6 +3374,10 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base-x@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-1.1.0.tgz#42d3d717474f9ea02207f6d1aa1f426913eeb7ac"
base-x@^3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77"
@ -3411,6 +3442,10 @@ bigi@^1.1.0, bigi@^1.4.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825"
bignumber.js@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.1.0.tgz#db6f14067c140bd46624815a7916c92d9b6c24b1"
bin-links@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-1.1.2.tgz#fb74bd54bae6b7befc6c6221f25322ac830d9757"
@ -3440,6 +3475,10 @@ bindings@^1.2.1, bindings@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
bip32-path@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/bip32-path/-/bip32-path-0.4.2.tgz#5db0416ad6822712f077836e2557b8697c0c7c99"
bip66@^1.1.0, bip66@^1.1.3:
version "1.1.5"
resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
@ -3497,6 +3536,10 @@ bluebird@~3.4.1:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
bn.js@^3.1.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-3.3.0.tgz#1138e577889fdc97bbdab51844f2190dfc0ae3d7"
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.0, bn.js@^4.11.3, bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@ -3581,7 +3624,7 @@ brcast@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/brcast/-/brcast-3.0.1.tgz#6256a8349b20de9eed44257a9b24d71493cd48dd"
brorand@^1.0.1:
brorand@^1.0.1, brorand@^1.0.5:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@ -5080,6 +5123,10 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
decimal.js@^5.0.8:
version "5.0.8"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-5.0.8.tgz#b48c3fb7d73a2d4d4940e0b38f1cd21db5b367ce"
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@ -5689,6 +5736,15 @@ elegant-spinner@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
elliptic@^5.1.0:
version "5.2.1"
resolved "http://registry.npmjs.org/elliptic/-/elliptic-5.2.1.tgz#fa294b6563c6ddbc9ba3dc8594687ae840858f10"
dependencies:
bn.js "^3.1.1"
brorand "^1.0.1"
hash.js "^1.0.0"
inherits "^2.0.1"
elliptic@^6.0.0, elliptic@^6.2.3:
version "6.4.0"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
@ -7405,7 +7461,7 @@ https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
https-proxy-agent@^2.1.0, https-proxy-agent@^2.2.0:
https-proxy-agent@2.2.1, https-proxy-agent@^2.1.0, https-proxy-agent@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
dependencies:
@ -8541,6 +8597,10 @@ jsonparse@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
jsonschema@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.2.tgz#83ab9c63d65bf4d596f91d81195e78772f6452bc"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@ -8907,7 +8967,7 @@ lodash@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
lodash@^4.0.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
lodash@^4.0.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@ -12007,6 +12067,82 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
ripple-address-codec@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz#eddbe3a7960d2e02c5c1c74fb9a9fa0d2dfb6571"
dependencies:
hash.js "^1.0.3"
x-address-codec "^0.7.0"
ripple-binary-codec@^0.1.0, ripple-binary-codec@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-0.1.13.tgz#c68951405a17a71695551e789966ff376da552e4"
dependencies:
babel-runtime "^6.6.1"
bn.js "^4.11.3"
create-hash "^1.1.2"
decimal.js "^5.0.8"
inherits "^2.0.1"
lodash "^4.12.0"
ripple-address-codec "^2.0.1"
ripple-bs58@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/ripple-bs58/-/ripple-bs58-4.0.1.tgz#b94d7acdc07cfd66906477cb4df39f07583f86a3"
dependencies:
base-x "^3.0.2"
ripple-bs58check@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripple-bs58check/-/ripple-bs58check-2.0.2.tgz#f270dbcd81630b26a21901c3ce27b7d62a4e9c91"
dependencies:
create-hash "^1.1.0"
ripple-bs58 "^4.0.0"
ripple-hashes@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/ripple-hashes/-/ripple-hashes-0.3.1.tgz#f2f46f1ff05e6487500a99839019114cd2482411"
dependencies:
bignumber.js "^4.1.0"
create-hash "^1.1.2"
ripple-address-codec "^2.0.1"
ripple-binary-codec "^0.1.0"
ripple-keypairs@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-0.10.1.tgz#ef796b519bb202682515e7cdd6063762636943e6"
dependencies:
babel-runtime "^5.8.20"
bn.js "^3.1.1"
brorand "^1.0.5"
elliptic "^5.1.0"
hash.js "^1.0.3"
ripple-address-codec "^2.0.1"
ripple-lib-transactionparser@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.6.2.tgz#eb117834816cab3398445a74ec3cacec95b6b5fa"
dependencies:
bignumber.js "^4.1.0"
lodash "^4.17.4"
ripple-lib@^1.0.0-beta.0:
version "1.0.0-beta.0"
resolved "https://registry.yarnpkg.com/ripple-lib/-/ripple-lib-1.0.0-beta.0.tgz#18d6284f9248044d04c39a17f02eb234a3e8a70f"
dependencies:
"@types/lodash" "^4.14.85"
"@types/ws" "^3.2.0"
bignumber.js "^4.1.0"
https-proxy-agent "2.2.1"
jsonschema "1.2.2"
lodash "^4.17.4"
ripple-address-codec "^2.0.1"
ripple-binary-codec "^0.1.13"
ripple-hashes "^0.3.1"
ripple-keypairs "^0.10.1"
ripple-lib-transactionparser "^0.6.2"
ws "^3.3.1"
rlp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.0.0.tgz#9db384ff4b89a8f61563d92395d8625b18f3afb0"
@ -13284,6 +13420,10 @@ uid-number@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
ultron@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
umask@^1.1.0, umask@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
@ -14043,6 +14183,14 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@^3.3.1:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
dependencies:
async-limiter "~1.0.0"
safe-buffer "~5.1.0"
ultron "~1.1.0"
ws@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289"
@ -14056,6 +14204,12 @@ ws@^5.1.1:
dependencies:
async-limiter "~1.0.0"
x-address-codec@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/x-address-codec/-/x-address-codec-0.7.2.tgz#2a2f7bb00278520bd13733a7959a05443d6802e0"
dependencies:
base-x "^1.0.1"
xdg-basedir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"

Loading…
Cancel
Save