diff --git a/public/data/appConfig.json b/public/data/appConfig.json index 1703c331..48221a88 100644 --- a/public/data/appConfig.json +++ b/public/data/appConfig.json @@ -15,7 +15,7 @@ "name": "Ethereum", "symbol": "ETH", "shortcut": "eth", - "bip44": "m/44'/60'/0'/0", + "bip44": "m/44'/60'/0'/0/a", "chainId": 1, "defaultGasPrice": 64, "defaultGasLimit": 21000, @@ -26,8 +26,8 @@ "wss://eth2.trezor.io/geth" ], "explorer": { - "tx": "https://etherscan.io/tx/", - "address": "https://etherscan.io/address/" + "tx": "https://eth1.trezor.io/tx/", + "address": "https://eth1.trezor.io/tx/" }, "hasSignVerify": true }, @@ -38,7 +38,7 @@ "symbol": "ETC", "shortcut": "etc", "chainId": 61, - "bip44": "m/44'/61'/0'/0", + "bip44": "m/44'/61'/0'/0/a", "defaultGasPrice": 64, "defaultGasLimit": 21000, "defaultGasLimitTokens": 200000, @@ -61,7 +61,7 @@ "symbol": "tROP", "shortcut": "trop", "chainId": 3, - "bip44": "m/44'/60'/0'/0", + "bip44": "m/44'/60'/0'/0/a", "defaultGasPrice": 64, "defaultGasLimit": 21000, "defaultGasLimitTokens": 200000, @@ -83,8 +83,8 @@ "wss://ropsten1.trezor.io/geth" ], "explorer": { - "tx": "https://ropsten.etherscan.io/tx/", - "address": "https://ropsten.etherscan.io/address/" + "tx": "https://ropsten1.trezor.io/tx/", + "address": "https://ropsten1.trezor.io/address/" }, "hasSignVerify": true }, diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 1e5e12c7..cf38efa6 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -76,32 +76,31 @@ export const subscribe = (networkName: string): PromiseAction => async ( } }; -export const onBlockMined = ( - payload: $ElementType -): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { +export const onBlockMined = (payload: BlockchainBlock): PromiseAction => async ( + dispatch: Dispatch, + getState: GetState +): Promise => { const shortcut = payload.coin.shortcut.toLowerCase(); - const { block } = payload; - if (getState().router.location.state.network !== shortcut) return; - const { config } = getState().localStorage; const network = config.networks.find(c => c.shortcut === shortcut); if (!network) return; switch (network.type) { case 'ethereum': - await dispatch(EthereumBlockchainActions.onBlockMined(shortcut)); + await dispatch(EthereumBlockchainActions.onBlockMined(network)); break; case 'ripple': - await dispatch(RippleBlockchainActions.onBlockMined(shortcut, block)); + await dispatch(RippleBlockchainActions.onBlockMined(network)); break; default: break; } }; -export const onNotification = ( - payload: $ElementType -): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { +export const onNotification = (payload: BlockchainNotification): PromiseAction => async ( + dispatch: Dispatch, + getState: GetState +): Promise => { const shortcut = payload.coin.shortcut.toLowerCase(); const { config } = getState().localStorage; const network = config.networks.find(c => c.shortcut === shortcut); @@ -109,11 +108,10 @@ export const onNotification = ( switch (network.type) { case 'ethereum': - // this is not working until blockchain-link will start support blockbook backends - await dispatch(EthereumBlockchainActions.onNotification(payload)); + await dispatch(EthereumBlockchainActions.onNotification(payload, network)); break; case 'ripple': - await dispatch(RippleBlockchainActions.onNotification(payload)); + await dispatch(RippleBlockchainActions.onNotification(payload, network)); break; default: break; @@ -122,9 +120,10 @@ export const onNotification = ( // Handle BLOCKCHAIN.ERROR event from TrezorConnect // disconnect and remove Web3 websocket instance if exists -export const onError = ( - payload: $ElementType -): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { +export const onError = (payload: BlockchainError): PromiseAction => async ( + dispatch: Dispatch, + getState: GetState +): Promise => { const shortcut = payload.coin.shortcut.toLowerCase(); const { config } = getState().localStorage; const network = config.networks.find(c => c.shortcut === shortcut); diff --git a/src/actions/ImportAccountActions.js b/src/actions/ImportAccountActions.js index 91a16277..9e779279 100644 --- a/src/actions/ImportAccountActions.js +++ b/src/actions/ImportAccountActions.js @@ -4,10 +4,9 @@ import * as ACCOUNT from 'actions/constants/account'; import * as IMPORT from 'actions/constants/importAccount'; import * as NOTIFICATION from 'actions/constants/notification'; import type { AsyncAction, Account, TrezorDevice, Network, Dispatch, GetState } from 'flowtype'; -import * as BlockchainActions from 'actions/ethereum/BlockchainActions'; import * as LocalStorageActions from 'actions/LocalStorageActions'; import TrezorConnect from 'trezor-connect'; -import { toDecimalAmount } from 'utils/formatUtils'; +import { enhanceAccount } from 'utils/accountUtils'; export type ImportAccountAction = | { @@ -23,10 +22,7 @@ export type ImportAccountAction = const findIndex = (accounts: Array, network: Network, device: TrezorDevice): number => { return accounts.filter( - a => - a.imported === true && - a.network === network.shortcut && - a.deviceID === (device.features || {}).device_id + a => a.imported === true && a.network === network.shortcut && a.deviceID === device.id ).length; }; @@ -35,113 +31,22 @@ export const importAddress = ( network: Network, device: ?TrezorDevice ): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - if (!device) return; + if (!device || !device.features) return; dispatch({ type: IMPORT.START, }); - let payload; - try { - if (network.type === 'ethereum') { - const account = await dispatch( - BlockchainActions.discoverAccount(device, address, network.shortcut) - ); - - const index = findIndex(getState().accounts, network, device); - - const empty = account.nonce <= 0 && account.balance === '0'; - payload = { - imported: true, - index, - network: network.shortcut, - deviceID: device.features ? device.features.device_id : '0', - deviceState: device.state || '0', - accountPath: account.path || [], - descriptor: account.descriptor, - - balance: account.balance, - availableBalance: account.balance, - block: account.block, - transactions: account.transactions, - empty, - - networkType: 'ethereum', - nonce: account.nonce, - }; - dispatch({ - type: ACCOUNT.CREATE, - payload, - }); - dispatch({ - type: IMPORT.SUCCESS, - }); - dispatch(LocalStorageActions.setImportedAccount(payload)); - dispatch({ - type: NOTIFICATION.ADD, - payload: { - variant: 'success', - title: 'The account has been successfully imported', - cancelable: true, - }, - }); - } else if (network.type === 'ripple') { - const response = await TrezorConnect.rippleGetAccountInfo({ - account: { - descriptor: address, - }, - coin: network.shortcut, - }); - - // handle TREZOR response error - if (!response.success) { - throw new Error(response.payload.error); - } - - const account = response.payload; - const empty = account.sequence <= 0 && account.balance === '0'; - const index = findIndex(getState().accounts, network, device); - - payload = { - imported: true, - index, - network: network.shortcut, - deviceID: device.features ? device.features.device_id : '0', - deviceState: device.state || '0', - accountPath: account.path || [], - descriptor: account.descriptor, - - balance: toDecimalAmount(account.balance, network.decimals), - availableBalance: toDecimalAmount(account.availableBalance, network.decimals), - block: account.block, - transactions: account.transactions, - empty, + const response = await TrezorConnect.getAccountInfo({ + descriptor: address, + coin: network.shortcut, + }); - networkType: 'ripple', - sequence: account.sequence, - reserve: toDecimalAmount(account.reserve, network.decimals), - }; - dispatch({ - type: ACCOUNT.CREATE, - payload, - }); - dispatch({ - type: IMPORT.SUCCESS, - }); - dispatch(LocalStorageActions.setImportedAccount(payload)); - dispatch({ - type: NOTIFICATION.ADD, - payload: { - variant: 'success', - title: 'The account has been successfully imported', - cancelable: true, - }, - }); - } - } catch (error) { + // handle TREZOR response error + if (!response.success) { dispatch({ type: IMPORT.FAIL, - error: error.message, + error: response.payload.error, }); dispatch({ @@ -149,9 +54,36 @@ export const importAddress = ( payload: { variant: 'error', title: 'Import account error', - message: error.message, + message: response.payload.error, cancelable: true, }, }); + + return; } + + const index = findIndex(getState().accounts, network, device); + const account = enhanceAccount(response.payload, { + imported: true, + index, + network, + device, + }); + + dispatch({ + type: ACCOUNT.CREATE, + payload: account, + }); + dispatch({ + type: IMPORT.SUCCESS, + }); + dispatch(LocalStorageActions.setImportedAccount(account)); + dispatch({ + type: NOTIFICATION.ADD, + payload: { + variant: 'success', + title: 'The account has been successfully imported', + cancelable: true, + }, + }); }; diff --git a/src/actions/ethereum/BlockchainActions.js b/src/actions/ethereum/BlockchainActions.js index 9ceb83e4..ac299de1 100644 --- a/src/actions/ethereum/BlockchainActions.js +++ b/src/actions/ethereum/BlockchainActions.js @@ -3,47 +3,14 @@ import TrezorConnect from 'trezor-connect'; import BigNumber from 'bignumber.js'; import * as PENDING from 'actions/constants/pendingTx'; +import * as AccountsActions from 'actions/AccountsActions'; +import * as Web3Actions from 'actions/Web3Actions'; +import { mergeAccount, enhanceTransaction } from 'utils/accountUtils'; -import type { TrezorDevice, Dispatch, GetState, PromiseAction } from 'flowtype'; -import type { EthereumAccount, BlockchainNotification } from 'trezor-connect'; +import type { Dispatch, GetState, PromiseAction, Network } from 'flowtype'; +import type { BlockchainNotification } from 'trezor-connect'; import type { Token } from 'reducers/TokensReducer'; import type { NetworkToken } from 'reducers/LocalStorageReducer'; -import * as Web3Actions from 'actions/Web3Actions'; -import * as AccountsActions from 'actions/AccountsActions'; - -export const discoverAccount = ( - device: TrezorDevice, - descriptor: string, - network: string -): PromiseAction => async (dispatch: Dispatch): Promise => { - // get data from connect - const txs = await TrezorConnect.ethereumGetAccountInfo({ - account: { - descriptor, - block: 0, - transactions: 0, - balance: '0', - availableBalance: '0', - nonce: 0, - }, - coin: network, - }); - - if (!txs.success) { - throw new Error(txs.payload.error); - } - - // blockbook web3 fallback - const web3account = await dispatch(Web3Actions.discoverAccount(descriptor, network)); - return { - descriptor, - transactions: txs.payload.transactions, - block: txs.payload.block, - balance: web3account.balance, - availableBalance: web3account.balance, - nonce: web3account.nonce, - }; -}; export const getTokenInfo = (input: string, network: string): PromiseAction => async ( dispatch: Dispatch @@ -103,9 +70,9 @@ export const subscribe = (network: string): PromiseAction => async ( dispatch: Dispatch, getState: GetState ): Promise => { - const accounts: Array = getState() + const accounts = getState() .accounts.filter(a => a.network === network) - .map(a => a.descriptor); // eslint-disable-line no-unused-vars + .map(a => ({ descriptor: a.descriptor })); const response = await TrezorConnect.blockchainSubscribe({ accounts, coin: network, @@ -115,7 +82,7 @@ export const subscribe = (network: string): PromiseAction => async ( await dispatch(Web3Actions.initWeb3(network)); }; -export const onBlockMined = (network: string): PromiseAction => async ( +export const onBlockMined = (network: Network): PromiseAction => async ( dispatch: Dispatch, getState: GetState ): Promise => { @@ -123,56 +90,52 @@ export const onBlockMined = (network: string): PromiseAction => async ( // check latest saved transaction blockhash against blockhheight // try to resolve pending transactions - await dispatch(Web3Actions.resolvePendingTransactions(network)); - - await dispatch(Web3Actions.updateGasPrice(network)); - - const accounts: Array = getState().accounts.filter(a => a.network === network); - if (accounts.length > 0) { - // find out which account changed - const response = await TrezorConnect.ethereumGetAccountInfo({ - accounts, - coin: network, - }); - - if (response.success) { - response.payload.forEach((a, i) => { - if (a.transactions > 0) { - // load additional data from Web3 (balance, nonce, tokens) - dispatch(Web3Actions.updateAccount(accounts[i], a, network)); - } else { - // there are no new txs, just update block - // TODO: There still could be internal transactions as a result of contract - // If that's the case, account balance won't be updated - // Currently waiting for deprecating web3 and utilising new blockbook - dispatch(AccountsActions.update({ ...accounts[i], block: a.block })); - - // HACK: since blockbook can't work with smart contracts for now - // try to update tokens balances added to this account using Web3 - dispatch(Web3Actions.updateAccountTokens(accounts[i])); - } - }); - } - } + await dispatch(Web3Actions.resolvePendingTransactions(network.shortcut)); + + await dispatch(Web3Actions.updateGasPrice(network.shortcut)); + + const accounts = getState().accounts.filter(a => a.network === network.shortcut); + if (accounts.length === 0) return; + const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut); + if (!blockchain) return; // flowtype fallback + + // find out which account changed + const bundle = accounts.map(a => ({ descriptor: a.descriptor, coin: network.shortcut })); + const response = await TrezorConnect.getAccountInfo({ bundle }); + + if (!response.success) return; + + response.payload.forEach((info, i) => { + dispatch( + AccountsActions.update(mergeAccount(info, accounts[i], network, blockchain.block)) + ); + dispatch(Web3Actions.updateAccountTokens(accounts[i])); + }); }; export const onNotification = ( - payload: $ElementType + payload: BlockchainNotification, + network: Network ): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const { notification } = payload; - const account = getState().accounts.find(a => a.descriptor === notification.descriptor); - if (!account) return; - - if (!notification.blockHeight) { - dispatch({ - type: PENDING.ADD, - payload: { - ...notification, - deviceState: account.deviceState, - network: account.network, - }, - }); - } + const { descriptor, tx } = payload.notification; + const account = getState().accounts.find(a => a.descriptor === descriptor); + const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut); + if (!account || !blockchain) return; + dispatch({ + type: PENDING.ADD, + payload: enhanceTransaction(account, tx, network), + }); + + const response = await TrezorConnect.getAccountInfo({ + descriptor: account.descriptor, + coin: account.network, + }); + + if (!response.success) return; + + dispatch( + AccountsActions.update(mergeAccount(response.payload, account, network, blockchain.block)) + ); }; export const onError = (network: string): PromiseAction => async ( diff --git a/src/actions/ethereum/DiscoveryActions.js b/src/actions/ethereum/DiscoveryActions.js index d7b8bc03..85864a31 100644 --- a/src/actions/ethereum/DiscoveryActions.js +++ b/src/actions/ethereum/DiscoveryActions.js @@ -1,10 +1,8 @@ /* @flow */ import TrezorConnect from 'trezor-connect'; -import EthereumjsUtil from 'ethereumjs-util'; import * as DISCOVERY from 'actions/constants/discovery'; -import * as BlockchainActions from 'actions/ethereum/BlockchainActions'; - +import { enhanceAccount } from 'utils/accountUtils'; import type { PromiseAction, Dispatch, GetState, TrezorDevice, Network, Account } from 'flowtype'; import type { Discovery } from 'reducers/DiscoveryReducer'; @@ -13,30 +11,41 @@ export type DiscoveryStartAction = { networkType: 'ethereum', network: Network, device: TrezorDevice, - publicKey: string, - chainCode: string, - basePath: Array, }; -// first iteration -// generate public key for this account -// start discovery process export const begin = ( device: TrezorDevice, network: Network -): PromiseAction => async (): Promise => { - // get xpub from TREZOR - const response = await TrezorConnect.getPublicKey({ +): PromiseAction => async (): Promise => ({ + type: DISCOVERY.START, + networkType: 'ethereum', + network, + device, +}); + +export const discoverAccount = ( + device: TrezorDevice, + discoveryProcess: Discovery +): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { config } = getState().localStorage; + const network = config.networks.find(c => c.shortcut === discoveryProcess.network); + if (!network) throw new Error('Discovery network not found'); + + const { accountIndex } = discoveryProcess; + const path = network.bip44.slice(0).replace('a', accountIndex.toString()); + + const response = await TrezorConnect.getAccountInfo({ device: { path: device.path, instance: device.instance, state: device.state, }, - path: network.bip44, + path, + // details: 'tokenBalances', TODO: load ERC20 + pageSize: 1, keepSession: true, // acquire and hold session - //useEmptyPassphrase: !device.instance, useEmptyPassphrase: device.useEmptyPassphrase, - network: network.name, + coin: network.shortcut, }); // handle TREZOR response error @@ -44,59 +53,9 @@ export const begin = ( throw new Error(response.payload.error); } - const basePath: Array = response.payload.path; - - return { - type: DISCOVERY.START, - networkType: 'ethereum', + return enhanceAccount(response.payload, { + index: discoveryProcess.accountIndex, network, device, - publicKey: response.payload.publicKey, - chainCode: response.payload.chainCode, - basePath, - }; -}; - -export const discoverAccount = ( - device: TrezorDevice, - discoveryProcess: Discovery -): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const { config } = getState().localStorage; - const network = config.networks.find(c => c.shortcut === discoveryProcess.network); - if (!network) throw new Error('Discovery network not found'); - - const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`); - const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex); - const publicAddress: string = EthereumjsUtil.publicToAddress( - derivedKey.publicKey, - true - ).toString('hex'); - const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress); - - // TODO: check if address was created before - const account = await dispatch( - BlockchainActions.discoverAccount(device, ethAddress, network.shortcut) - ); - - // const accountIsEmpty = account.transactions <= 0 && account.nonce <= 0 && account.balance === '0'; - const empty = account.nonce <= 0 && account.balance === '0'; - - return { - imported: false, - index: discoveryProcess.accountIndex, - network: network.shortcut, - deviceID: device.features ? device.features.device_id : '0', - deviceState: device.state || '0', - accountPath: path, - descriptor: ethAddress, - - balance: account.balance, - availableBalance: account.balance, - block: account.block, - transactions: account.transactions, - empty, - - networkType: 'ethereum', - nonce: account.nonce, - }; + }); }; diff --git a/src/actions/ripple/BlockchainActions.js b/src/actions/ripple/BlockchainActions.js index 8d19b9af..70d3ab87 100644 --- a/src/actions/ripple/BlockchainActions.js +++ b/src/actions/ripple/BlockchainActions.js @@ -4,7 +4,7 @@ import TrezorConnect from 'trezor-connect'; import * as BLOCKCHAIN from 'actions/constants/blockchain'; import * as PENDING from 'actions/constants/pendingTx'; import * as AccountsActions from 'actions/AccountsActions'; -import { toDecimalAmount } from 'utils/formatUtils'; +import { mergeAccount, enhanceTransaction } from 'utils/accountUtils'; import { observeChanges } from 'reducers/utils'; import type { BlockchainNotification } from 'trezor-connect'; @@ -20,10 +20,10 @@ import type { export const subscribe = (network: string): PromiseAction => async ( dispatch: Dispatch, getState: GetState -): Promise => { - const accounts: Array = getState() +) => { + const accounts = getState() .accounts.filter(a => a.network === network) - .map(a => a.descriptor); + .map(a => ({ descriptor: a.descriptor })); await TrezorConnect.blockchainSubscribe({ accounts, coin: network, @@ -47,161 +47,81 @@ export const getFeeLevels = (network: Network): PayloadAction => async ( +export const onBlockMined = (network: Network): PromiseAction => async ( dispatch: Dispatch, getState: GetState ): Promise => { - const blockchain = getState().blockchain.find(b => b.shortcut === networkShortcut); + const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut); if (!blockchain) return; // flowtype fallback // if last update was more than 5 minutes ago const now = new Date().getTime(); if (blockchain.feeTimestamp < now - 300000) { const feeRequest = await TrezorConnect.blockchainEstimateFee({ - coin: networkShortcut, + request: { + feeLevels: 'smart', + }, + coin: network.shortcut, }); if (feeRequest.success && observeChanges(blockchain.feeLevels, feeRequest.payload)) { // check if downloaded fee levels are different dispatch({ type: BLOCKCHAIN.UPDATE_FEE, - shortcut: networkShortcut, - feeLevels: feeRequest.payload, + shortcut: network.shortcut, + feeLevels: feeRequest.payload.levels.map(l => ({ + name: 'Normal', + value: l.feePerUnit, + })), }); } } // TODO: check for blockchain rollbacks here! - const accounts: Array = getState().accounts.filter(a => a.network === networkShortcut); + const accounts = getState().accounts.filter(a => a.network === network.shortcut); if (accounts.length === 0) return; - const { networks } = getState().localStorage.config; - const network = networks.find(c => c.shortcut === networkShortcut); - if (!network) return; - - // HACK: Since Connect always returns account.transactions as 0 - // we don't have info about new transactions for the account since last update. - // Untill there is a better solution compare accounts block. - // If we missed some blocks (wallet was offline) we'll update the account - // If we are update to date with the last block that means wallet was online - // and we would get Blockchain notification about new transaction if needed - accounts.forEach(async account => { - const missingBlocks = account.block !== block - 1; - if (!missingBlocks) { - // account was last updated on account.block, current block is +1, we didn't miss single block - // if there was new tx, blockchain notification would let us know - // so just update the block for the account - dispatch( - AccountsActions.update({ - ...account, - block, - }) - ); - } else { - // we missed some blocks (wallet was offline). get updated account info from connect - const response = await TrezorConnect.rippleGetAccountInfo({ - account: { - descriptor: account.descriptor, - }, - level: 'transactions', - coin: networkShortcut, - }); - if (!response.success) return; - - const updatedAccount = response.payload; - - // new txs - dispatch( - AccountsActions.update({ - ...account, - balance: toDecimalAmount(updatedAccount.balance, network.decimals), - availableBalance: toDecimalAmount( - updatedAccount.availableBalance, - network.decimals - ), - block: updatedAccount.block, - sequence: updatedAccount.sequence, - }) - ); - } + const bundle = accounts.map(a => ({ descriptor: a.descriptor, coin: network.shortcut })); + const response = await TrezorConnect.getAccountInfo({ bundle }); + + if (!response.success) return; + + response.payload.forEach((info, i) => { + dispatch( + AccountsActions.update(mergeAccount(info, accounts[i], network, blockchain.block)) + ); }); }; export const onNotification = ( - payload: $ElementType + payload: BlockchainNotification, + network: Network ): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const { notification } = payload; - const account = getState().accounts.find(a => a.descriptor === notification.descriptor); - if (!account) return; - const { network } = getState().selectedAccount; - if (!network) return; // flowtype fallback + const { descriptor, tx } = payload.notification; + const account = getState().accounts.find(a => a.descriptor === descriptor); + const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut); + if (!account || !blockchain) return; - if (!notification.blockHeight) { + if (!tx.blockHeight) { dispatch({ type: PENDING.ADD, - payload: { - ...notification, - deviceState: account.deviceState, - network: account.network, - - amount: toDecimalAmount(notification.amount, network.decimals), - total: - notification.type === 'send' - ? toDecimalAmount(notification.total, network.decimals) - : toDecimalAmount(notification.amount, network.decimals), - fee: toDecimalAmount(notification.fee, network.decimals), - }, + payload: enhanceTransaction(account, tx, network), }); - - // todo: replace "send success" notification with link to explorer } else { dispatch({ type: PENDING.TX_RESOLVED, - hash: notification.hash, + hash: tx.txid, }); } - // In case of tx sent between two Trezor accounts there is a possibility that only 1 notification will be received - // therefore we need to find target account and update data for it as well - const accountsToUpdate = [account]; - const targetAddress = - notification.type === 'send' - ? notification.outputs[0].addresses[0] - : notification.inputs[0].addresses[0]; - - const targetAccount = getState().accounts.find(a => a.descriptor === targetAddress); - if (targetAccount) { - accountsToUpdate.push(targetAccount); - } + const response = await TrezorConnect.getAccountInfo({ + descriptor: account.descriptor, + coin: account.network, + }); - accountsToUpdate.forEach(async a => { - const response = await TrezorConnect.rippleGetAccountInfo({ - account: { - descriptor: a.descriptor, - from: a.block, - history: false, - }, - coin: a.network, - }); + if (!response.success) return; - if (response.success) { - const updatedAccount = response.payload; - const empty = updatedAccount.sequence <= 0 && updatedAccount.balance === '0'; - dispatch( - AccountsActions.update({ - networkType: 'ripple', - ...a, - balance: toDecimalAmount(updatedAccount.balance, network.decimals), - availableBalance: toDecimalAmount( - updatedAccount.availableBalance, - network.decimals - ), - block: updatedAccount.block, - sequence: updatedAccount.sequence, - reserve: toDecimalAmount(updatedAccount.reserve, network.decimals), - empty, - }) - ); - } - }); + dispatch( + AccountsActions.update(mergeAccount(response.payload, account, network, blockchain.block)) + ); }; diff --git a/src/actions/ripple/DiscoveryActions.js b/src/actions/ripple/DiscoveryActions.js index 6ae2a332..0683b3da 100644 --- a/src/actions/ripple/DiscoveryActions.js +++ b/src/actions/ripple/DiscoveryActions.js @@ -2,7 +2,7 @@ import TrezorConnect from 'trezor-connect'; import * as DISCOVERY from 'actions/constants/discovery'; -import { toDecimalAmount } from 'utils/formatUtils'; +import { enhanceAccount } from 'utils/accountUtils'; import type { PromiseAction, GetState, Dispatch, TrezorDevice, Network, Account } from 'flowtype'; import type { Discovery } from 'reducers/DiscoveryReducer'; @@ -35,16 +35,13 @@ export const discoverAccount = ( const { accountIndex } = discoveryProcess; const path = network.bip44.slice(0).replace('a', accountIndex.toString()); - const response = await TrezorConnect.rippleGetAccountInfo({ + const response = await TrezorConnect.getAccountInfo({ device: { path: device.path, instance: device.instance, state: device.state, }, - account: { - path, - block: 0, - }, + path, keepSession: true, // acquire and hold session useEmptyPassphrase: device.useEmptyPassphrase, coin: network.shortcut, @@ -55,26 +52,9 @@ export const discoverAccount = ( throw new Error(response.payload.error); } - const account = response.payload; - const empty = account.sequence <= 0 && account.balance === '0'; - - return { - imported: false, + return enhanceAccount(response.payload, { index: discoveryProcess.accountIndex, - network: network.shortcut, - deviceID: device.features ? device.features.device_id : '0', - deviceState: device.state || '0', - accountPath: account.path || [], - descriptor: account.descriptor, - - balance: toDecimalAmount(account.balance, network.decimals), - availableBalance: toDecimalAmount(account.availableBalance, network.decimals), - block: account.block, - transactions: account.transactions, - empty, - - networkType: 'ripple', - sequence: account.sequence, - reserve: toDecimalAmount(account.reserve, network.decimals), - }; + network, + device, + }); }; diff --git a/src/reducers/AccountsReducer.js b/src/reducers/AccountsReducer.js index 22994c3f..2b643aba 100644 --- a/src/reducers/AccountsReducer.js +++ b/src/reducers/AccountsReducer.js @@ -6,13 +6,13 @@ import * as ACCOUNT from 'actions/constants/account'; import type { Action, TrezorDevice } from 'flowtype'; -type AccountCommon = { +type AccountCommon = {| +imported: boolean, +index: number, +network: string, // network id (shortcut) +deviceID: string, // empty for imported accounts +deviceState: string, // empty for imported accounts - +accountPath: Array, // empty for imported accounts + +accountPath: string, // empty for imported accounts +descriptor: string, // address or xpub balance: string, @@ -21,22 +21,31 @@ type AccountCommon = { empty: boolean, // account without transactions transactions: number, // deprecated -}; +|}; export type Account = - | (AccountCommon & { - networkType: 'ethereum', - nonce: number, - }) - | (AccountCommon & { - networkType: 'ripple', - sequence: number, - reserve: string, - }) - | (AccountCommon & { - networkType: 'bitcoin', - addressIndex: number, - }); + | {| + ...AccountCommon, + ...{| + networkType: 'ethereum', + nonce: string, + |}, + |} + | {| + ...AccountCommon, + ...{| + networkType: 'ripple', + sequence: number, + reserve: string, + |}, + |} + | {| + ...AccountCommon, + ...{| + networkType: 'bitcoin', + addressIndex: number, + |}, + |}; export type State = Array; @@ -51,14 +60,12 @@ export const findDeviceAccounts = ( return state.filter( addr => (addr.deviceState === device.state || - (addr.imported && addr.deviceID === (device.features || {}).device_id)) && + (addr.imported && addr.deviceID === device.id)) && addr.network === network ); } return state.filter( - addr => - addr.deviceState === device.state || - (addr.imported && addr.deviceID === (device.features || {}).device_id) + addr => addr.deviceState === device.state || (addr.imported && addr.deviceID === device.id) ); }; diff --git a/src/reducers/BlockchainReducer.js b/src/reducers/BlockchainReducer.js index d6a1e029..fd545ab4 100644 --- a/src/reducers/BlockchainReducer.js +++ b/src/reducers/BlockchainReducer.js @@ -4,7 +4,7 @@ import { BLOCKCHAIN as BLOCKCHAIN_EVENT } from 'trezor-connect'; import * as BLOCKCHAIN_ACTION from 'actions/constants/blockchain'; import type { Action } from 'flowtype'; -import type { BlockchainConnect, BlockchainError, BlockchainBlock } from 'trezor-connect'; +import type { BlockchainInfo, BlockchainError, BlockchainBlock } from 'trezor-connect'; export type BlockchainFeeLevel = { name: string, @@ -50,16 +50,15 @@ const onStartSubscribe = (state: State, shortcut: string): State => { ]); }; -const onConnect = (state: State, action: BlockchainConnect): State => { - const shortcut = action.payload.coin.shortcut.toLowerCase(); +const onConnect = (state: State, info: BlockchainInfo): State => { + const shortcut = info.coin.shortcut.toLowerCase(); const network = state.find(b => b.shortcut === shortcut); - const { info } = action.payload; if (network) { const others = state.filter(b => b !== network); return others.concat([ { ...network, - block: info.block, + block: info.blockHeight, connected: true, connecting: false, reconnectionAttempts: 0, @@ -73,15 +72,15 @@ const onConnect = (state: State, action: BlockchainConnect): State => { connected: true, connecting: false, reconnectionAttempts: 0, - block: info.block, + block: info.blockHeight, feeTimestamp: 0, feeLevels: [], }, ]); }; -const onError = (state: State, action: BlockchainError): State => { - const shortcut = action.payload.coin.shortcut.toLowerCase(); +const onError = (state: State, payload: BlockchainError): State => { + const shortcut = payload.coin.shortcut.toLowerCase(); const network = state.find(b => b.shortcut === shortcut); if (network) { const others = state.filter(b => b !== network); @@ -108,15 +107,15 @@ const onError = (state: State, action: BlockchainError): State => { ]); }; -const onBlock = (state: State, action: BlockchainBlock): State => { - const shortcut = action.payload.coin.shortcut.toLowerCase(); +const onBlock = (state: State, payload: BlockchainBlock): State => { + const shortcut = payload.coin.shortcut.toLowerCase(); const network = state.find(b => b.shortcut === shortcut); if (network) { const others = state.filter(b => b !== network); return others.concat([ { ...network, - block: action.payload.block, + block: payload.blockHeight, }, ]); } @@ -143,11 +142,11 @@ export default (state: State = initialState, action: Action): State => { case BLOCKCHAIN_ACTION.START_SUBSCRIBE: return onStartSubscribe(state, action.shortcut); case BLOCKCHAIN_EVENT.CONNECT: - return onConnect(state, action); + return onConnect(state, action.payload); case BLOCKCHAIN_EVENT.ERROR: - return onError(state, action); + return onError(state, action.payload); case BLOCKCHAIN_EVENT.BLOCK: - return onBlock(state, action); + return onBlock(state, action.payload); case BLOCKCHAIN_ACTION.UPDATE_FEE: return updateFee(state, action.shortcut, action.feeLevels); diff --git a/src/reducers/DiscoveryReducer.js b/src/reducers/DiscoveryReducer.js index 30b42061..ec7acb81 100644 --- a/src/reducers/DiscoveryReducer.js +++ b/src/reducers/DiscoveryReducer.js @@ -1,7 +1,5 @@ /* @flow */ -import HDKey from 'hdkey'; - import * as DISCOVERY from 'actions/constants/discovery'; import * as ACCOUNT from 'actions/constants/account'; import * as CONNECT from 'actions/constants/TrezorConnect'; @@ -27,10 +25,6 @@ export type Discovery = { waitingForBlockchain: boolean, fwNotSupported: boolean, fwOutdated: boolean, - - publicKey: string, // used in ethereum only - chainCode: string, // used in ethereum only - hdKey: HDKey, // used in ethereum only }; export type State = Array; @@ -46,10 +40,6 @@ const defaultDiscovery: Discovery = { waitingForBlockchain: false, fwNotSupported: false, fwOutdated: false, - - publicKey: '', - chainCode: '', - hdKey: null, }; const findIndex = (state: State, network: string, deviceState: string): number => @@ -63,18 +53,6 @@ const start = (state: State, action: DiscoveryStartAction): State => { deviceState, }; - if (action.networkType === 'ethereum') { - const hdKey = new HDKey(); - hdKey.publicKey = Buffer.from(action.publicKey, 'hex'); - hdKey.chainCode = Buffer.from(action.chainCode, 'hex'); - - instance.hdKey = hdKey; - instance.publicKey = action.publicKey; - instance.chainCode = action.chainCode; - - instance.basePath = action.basePath; - } - const newState: State = [...state]; const index: number = findIndex(state, action.network.shortcut, deviceState); if (index >= 0) { @@ -202,15 +180,8 @@ export default function discovery(state: State = initialState, action: Action): return notSupported(state, action); case DISCOVERY.FROM_STORAGE: return action.payload.map(d => { - if (d.publicKey.length < 1) return d; - // recreate ethereum discovery HDKey - // deprecated: will be removed after switching to blockbook - const hdKey: HDKey = new HDKey(); - hdKey.publicKey = Buffer.from(d.publicKey, 'hex'); - hdKey.chainCode = Buffer.from(d.chainCode, 'hex'); return { ...d, - hdKey, interrupted: false, waitingForDevice: false, waitingForBlockchain: false,