From 26978fe9840704071e5c9dbd53fbb902b2f83d7c Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 12 Sep 2018 13:25:21 +0200 Subject: [PATCH] web3 actions splitted to blockchain actions --- src/actions/AccountsActions.js | 28 +- src/actions/BlockchainActions.js | 7 +- src/actions/DiscoveryActions.js | 272 +++++++----- src/actions/PendingTxActions.js | 3 + src/actions/SendFormActions.js | 138 +++--- src/actions/TokenActions.js | 13 +- src/actions/TrezorConnectActions.js | 17 +- src/actions/Web3Actions.js | 608 +++++++++++++-------------- src/actions/constants/account.js | 1 + src/actions/constants/pendingTx.js | 1 + src/services/TrezorConnectService.js | 12 +- 11 files changed, 590 insertions(+), 510 deletions(-) diff --git a/src/actions/AccountsActions.js b/src/actions/AccountsActions.js index 2bc3a8f1..65ab5d50 100644 --- a/src/actions/AccountsActions.js +++ b/src/actions/AccountsActions.js @@ -2,7 +2,7 @@ import * as ACCOUNT from 'actions/constants/account'; import type { Action, TrezorDevice } from 'flowtype'; -import type { State } from 'reducers/AccountsReducer'; +import type { Account, State } from 'reducers/AccountsReducer'; export type AccountFromStorageAction = { type: typeof ACCOUNT.FROM_STORAGE, @@ -11,11 +11,12 @@ export type AccountFromStorageAction = { export type AccountCreateAction = { type: typeof ACCOUNT.CREATE, - device: TrezorDevice, - network: string, - index: number, - path: Array, - address: string + payload: Account, +} + +export type AccountUpdateAction = { + type: typeof ACCOUNT.UPDATE, + payload: Account, } export type AccountSetBalanceAction = { @@ -36,9 +37,10 @@ export type AccountSetNonceAction = { export type AccountAction = AccountFromStorageAction - | AccountCreateAction - | AccountSetBalanceAction - | AccountSetNonceAction; + | AccountCreateAction + | AccountUpdateAction + | AccountSetBalanceAction + | AccountSetNonceAction; export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => ({ type: ACCOUNT.SET_BALANCE, @@ -55,3 +57,11 @@ export const setNonce = (address: string, network: string, deviceState: string, deviceState, nonce, }); +<<<<<<< HEAD +======= + +export const update = (account: Account): Action => ({ + type: ACCOUNT.UPDATE, + payload: account +}); +>>>>>>> web3 actions splitted to blockchain actions diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 72f9c111..3e63144f 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -116,7 +116,7 @@ export const onBlockMined = (network: string): PromiseAction => async (dis } else { response.payload.forEach((a, i) => { if (a.transactions > 0) { - dispatch( Web3Actions.updateAccount(accounts[i], a, network) ) + // dispatch( Web3Actions.updateAccount(accounts[i], a, network) ) } }); } @@ -134,16 +134,14 @@ export const onNotification = (payload: any): PromiseAction => async (disp // const exists = getState().pending.filter(p => p.id === payload.tx.txid && p.address === address); const exists = getState().pending.filter(p => { - console.warn("CHECK", p.address === address, p.id === payload.tx.txid, p) return p.address === address; }); if (exists.length < 1) { - - console.warn("TXINFO!", txInfo); if (txInfo) { dispatch({ type: PENDING.ADD, payload: { + type: 'send', id: payload.tx.txid, network: payload.coin, currency: "tETH", @@ -152,6 +150,7 @@ export const onNotification = (payload: any): PromiseAction => async (disp tx: {}, nonce: txInfo.nonce, address, + rejected: false } }); } else { diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index b31534a0..2f04b96d 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -10,8 +10,10 @@ import type { } from 'flowtype'; import type { Discovery, State } from 'reducers/DiscoveryReducer'; import * as AccountsActions from './AccountsActions'; +import * as Web3Actions from './Web3Actions'; -import { getNonceAsync, getBalanceAsync } from './Web3Actions'; +import * as BlockchainActions from './BlockchainActions'; +import { setBalance as setTokenBalance } from './TokenActions'; export type DiscoveryStartAction = { @@ -44,15 +46,123 @@ export type DiscoveryAction = { type: typeof DISCOVERY.FROM_STORAGE, payload: State } | DiscoveryStartAction - | DiscoveryWaitingAction - | DiscoveryStopAction - | DiscoveryCompleteAction; + | DiscoveryWaitingAction + | DiscoveryStopAction + | DiscoveryCompleteAction; +export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const selected = getState().wallet.selectedDevice; + if (!selected) { + // TODO: throw error + console.error('Start discovery: no selected device', device); + return; + } if (selected.path !== device.path) { + console.error('Start discovery: requested device is not selected', device, selected); + return; + } if (!selected.state) { + console.warn("Start discovery: Selected device wasn't authenticated yet..."); + return; + } if (selected.connected && !selected.available) { + console.warn('Start discovery: Selected device is unavailable...'); + return; + } + + const discovery: State = getState().discovery; + const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network); + + if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { + dispatch({ + type: DISCOVERY.WAITING_FOR_DEVICE, + device, + network, + }); + return; + } + + const blockchain = getState().blockchain.find(b => b.name === network); + if (blockchain && !blockchain.connected) { + console.error("NO BACKEND!") // TODO + return; + } + + if (!discoveryProcess) { + dispatch(begin(device, network)) + } else if (discoveryProcess.completed && !ignoreCompleted) { + dispatch({ + type: DISCOVERY.COMPLETE, + device, + network, + }); + } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) { + // discovery cycle was interrupted + // start from beginning + dispatch(begin(device, network)); + } else { + dispatch(discoverAccount(device, discoveryProcess)); + } +}; -// Because start() is calling begin() and begin() is calling start() one of them must be declared first -// otherwise eslint will start complaining -let begin; +// first iteration +// generate public key for this account +// start discovery process +const begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { config } = getState().localStorage; + const coinToDiscover = config.coins.find(c => c.network === network); + if (!coinToDiscover) return; + dispatch({ + type: DISCOVERY.WAITING_FOR_DEVICE, + device, + network, + }); + + // get xpub from TREZOR + const response = await TrezorConnect.getPublicKey({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + path: coinToDiscover.bip44, + keepSession: true, // acquire and hold session + useEmptyPassphrase: !device.instance, + }); + + // handle TREZOR response error + if (!response.success) { + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Discovery error', + message: response.payload.error, + cancelable: true, + actions: [ + { + label: 'Try again', + callback: () => { + dispatch(start(device, network)); + }, + }, + ], + }, + }); + return; + } + + // send data to reducer + dispatch({ + type: DISCOVERY.START, + network: coinToDiscover.network, + device, + publicKey: response.payload.publicKey, + chainCode: response.payload.chainCode, + basePath, + }); + + // next iteration + dispatch(start(device, network)); +}; const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { const { completed } = discoveryProcess; @@ -66,64 +176,53 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy // TODO: check if address was created before - // verify address with TREZOR - const verifyAddress = await TrezorConnect.ethereumGetAddress({ - device: { - path: device.path, - instance: device.instance, - state: device.state, - }, - path, - showOnTrezor: false, - keepSession: true, - useEmptyPassphrase: !device.instance, - }); - if (discoveryProcess.interrupted) return; + try { + const account = await dispatch( BlockchainActions.discoverAccount(device, ethAddress, network) ); + if (discoveryProcess.interrupted) return; - // TODO: with block-book (Martin) - // const discoveryA = await TrezorConnect.accountDiscovery({ - // device: { - // path: device.path, - // instance: device.instance, - // state: device.state - // }, - // }); - // if (discoveryProcess.interrupted) return; - - if (verifyAddress && verifyAddress.success) { - //const trezorAddress: string = '0x' + verifyAddress.payload.address; - const trezorAddress: string = EthereumjsUtil.toChecksumAddress(verifyAddress.payload.address); - if (trezorAddress !== ethAddress) { - // throw inconsistent state error - console.warn('Inconsistent state', trezorAddress, ethAddress); + // const accountIsEmpty = account.transactions <= 0 && account.nonce <= 0 && account.balance === '0'; + const accountIsEmpty = account.nonce <= 0 && account.balance === '0'; + if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && discoveryProcess.accountIndex === 0)) { dispatch({ - type: NOTIFICATION.ADD, + type: ACCOUNT.CREATE, payload: { - type: 'error', - title: 'Address validation error', - message: `Addresses are different. TREZOR: ${trezorAddress} HDKey: ${ethAddress}`, - cancelable: true, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch(start(device, discoveryProcess.network)); - }, - }, - ], - }, + index: discoveryProcess.accountIndex, + loaded: true, + network, + deviceID: device.features ? device.features.device_id : '0', + deviceState: device.state || '0', + addressPath: path, + address: ethAddress, + balance: account.balance, + nonce: account.nonce, + block: account.block, + transactions: account.transactions + } }); - return; + + dispatch( Web3Actions.getTxInput() ); } - } else { - // handle TREZOR communication error + + if (accountIsEmpty) { + dispatch( finish(device, discoveryProcess) ); + } else { + if (!completed) { dispatch( discoverAccount(device, discoveryProcess) ); } + } + + } catch (error) { + + dispatch({ + type: DISCOVERY.STOP, + device + }); + dispatch({ type: NOTIFICATION.ADD, payload: { type: 'error', - title: 'Address validation error', - message: verifyAddress.payload.error, + title: 'Account discovery error', + message: error.message, cancelable: true, actions: [ { @@ -135,56 +234,33 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy ], }, }); - return; } +}; - const web3instance = getState().web3.find(w3 => w3.network === network); - if (!web3instance) return; +const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const balance = await getBalanceAsync(web3instance.web3, ethAddress); - if (discoveryProcess.interrupted) return; - const nonce: number = await getNonceAsync(web3instance.web3, ethAddress); - if (discoveryProcess.interrupted) return; + console.warn("FINISH!"); + await TrezorConnect.getFeatures({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + keepSession: false, + useEmptyPassphrase: !device.instance, + }); - const addressIsEmpty = nonce < 1 && !balance.greaterThan(0); + await dispatch( BlockchainActions.subscribe(discoveryProcess.network) ); - if (!addressIsEmpty || (addressIsEmpty && completed) || (addressIsEmpty && discoveryProcess.accountIndex === 0)) { - dispatch({ - type: ACCOUNT.CREATE, - device, - network, - index: discoveryProcess.accountIndex, - path, - address: ethAddress, - }); - dispatch( - AccountsActions.setBalance(ethAddress, network, device.state || 'undefined', web3instance.web3.fromWei(balance.toString(), 'ether')), - ); - dispatch(AccountsActions.setNonce(ethAddress, network, device.state || 'undefined', nonce)); - - if (!completed) { dispatch(discoverAccount(device, discoveryProcess)); } - } + if (discoveryProcess.interrupted) return; - if (addressIsEmpty) { - // release acquired sesssion - await TrezorConnect.getFeatures({ - device: { - path: device.path, - instance: device.instance, - state: device.state, - }, - keepSession: false, - useEmptyPassphrase: !device.instance, - }); - if (discoveryProcess.interrupted) return; + dispatch({ + type: DISCOVERY.COMPLETE, + device, + network: discoveryProcess.network, + }); - dispatch({ - type: DISCOVERY.COMPLETE, - device, - network, - }); - } -}; +} export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { const selected = getState().wallet.selectedDevice; diff --git a/src/actions/PendingTxActions.js b/src/actions/PendingTxActions.js index f9ebb839..b4f398d3 100644 --- a/src/actions/PendingTxActions.js +++ b/src/actions/PendingTxActions.js @@ -7,6 +7,9 @@ import type { State, PendingTx } from 'reducers/PendingTxReducer'; export type PendingTxAction = { type: typeof PENDING.FROM_STORAGE, payload: State +} | { + type: typeof PENDING.ADD, + payload: PendingTx } | { type: typeof PENDING.TX_RESOLVED, tx: PendingTx, diff --git a/src/actions/SendFormActions.js b/src/actions/SendFormActions.js index ae2189bd..ae5165d1 100644 --- a/src/actions/SendFormActions.js +++ b/src/actions/SendFormActions.js @@ -27,7 +27,8 @@ import type { State, FeeLevel } from 'reducers/SendFormReducer'; import type { Account } from 'reducers/AccountsReducer'; import type { Props } from 'views/Wallet/views/AccountSend/Container'; import * as SessionStorageActions from './SessionStorageActions'; -import { estimateGas, pushTx } from './Web3Actions'; +import { prepareEthereumTx, serializeEthereumTx } from './TxActions'; +import * as BlockchainActions from './BlockchainActions'; export type SendTxAction = { type: typeof SEND.TX_COMPLETE, @@ -223,14 +224,13 @@ export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLi // initialize component -export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { +export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { const { account, network, - web3, } = getState().selectedAccount; - if (!account || !network || !web3) return; + if (!account || !network) return; const stateFromStorage = SessionStorageActions.load(getState().router.location.pathname); if (stateFromStorage) { @@ -243,7 +243,10 @@ export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): // TODO: check if there are some unfinished tx in localStorage - const gasPrice: BigNumber = new BigNumber(EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei')) || new BigNumber(network.defaultGasPrice); + + // const gasPrice: BigNumber = new BigNumber(EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei')) || new BigNumber(network.defaultGasPrice); + const gasPrice: BigNumber = await dispatch( BlockchainActions.getGasPrice(network.network, network.defaultGasPrice) ); + // const gasPrice: BigNumber = new BigNumber(network.defaultGasPrice); const gasLimit: string = network.defaultGasLimit.toString(); const feeLevels: Array = getFeeLevels(network.symbol, gasPrice, gasLimit); @@ -709,12 +712,9 @@ export const onNonceChange = (nonce: string): AsyncAction => async (dispatch: Di const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { const { - web3, network, } = getState().selectedAccount; - if (!web3 || !network) return; - - const w3 = web3.web3; + if (!network) return; const state: State = getState().sendForm; const requestedData = state.data; @@ -732,14 +732,7 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: return; } - // TODO: allow data starting with 0x ... - const data: string = `0x${state.data.length % 2 === 0 ? state.data : `0${state.data}`}`; - const gasLimit = await estimateGas(w3, { - to: '0x0000000000000000000000000000000000000000', - data, - value: w3.toHex(w3.toWei(state.amount, 'ether')), - gasPrice: w3.toHex(EthereumjsUnits.convert(state.gasPrice, 'gwei', 'wei')), - }); + const gasLimit: number = await dispatch( BlockchainActions.estimateGasLimit(network.network, state.data, state.amount, state.gasPrice) ); if (getState().sendForm.data === requestedData) { dispatch(onGasLimitChange(gasLimit.toString())); @@ -777,56 +770,69 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge const { account, network, - web3, pending, } = getState().selectedAccount; - if (!account || !web3 || !network) return; + + if (!account || !network) return; const currentState: State = getState().sendForm; const isToken: boolean = currentState.currency !== currentState.networkSymbol; - const w3 = web3.web3; - const address_n = account.addressPath; - - let data: string = `0x${currentState.data}`; - let txAmount: string = w3.toHex(w3.toWei(currentState.amount, 'ether')); - let txAddress: string = currentState.address; - if (isToken) { - const token: ?Token = findToken(getState().tokens, account.address, currentState.currency, account.deviceState); - if (!token) return; - - const contract = web3.erc20.at(token.address); - const amountValue: string = new BigNumber(currentState.amount).times(Math.pow(10, token.decimals)).toString(10); - - data = contract.transfer.getData(currentState.address, amountValue, { - from: account.address, - gasLimit: currentState.gasLimit, - gasPrice: currentState.gasPrice, - }); - txAmount = '0x00'; - txAddress = token.address; - } - - const pendingNonce: number = getPendingNonce(pending); + const pendingNonce: number = stateUtils.getPendingNonce(pending); const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce; - console.warn('NONCE', nonce, account.nonce, pendingNonce); - - const txData = { - address_n, - // from: currentAddress.address - to: txAddress, - value: txAmount, - data, - chainId: web3.chainId, - nonce: w3.toHex(nonce), - gasLimit: w3.toHex(currentState.gasLimit), - gasPrice: w3.toHex(EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei')), - r: '', - s: '', - v: '', - }; + console.warn("NONCE", nonce); + + const txData = await dispatch( prepareEthereumTx({ + network: network.network, + token: isToken ? findToken(getState().tokens, account.address, currentState.currency, account.deviceState) : null, + from: account.address, + to: currentState.address, + amount: currentState.amount, + data: currentState.data, + gasLimit: currentState.gasLimit, + gasPrice: currentState.gasPrice, + nonce + }) ); + + // let data: string = `0x${currentState.data}`; + // let txAmount: string = w3.toHex(w3.toWei(currentState.amount, 'ether')); + // let txAddress: string = currentState.address; + // if (isToken) { + // const token: ?Token = findToken(getState().tokens, account.address, currentState.currency, account.deviceState); + // if (!token) return; + + // const contract = web3.erc20.at(token.address); + // const amountValue: string = new BigNumber(currentState.amount).times(Math.pow(10, token.decimals)).toString(10); + + // data = contract.transfer.getData(currentState.address, amountValue, { + // from: account.address, + // gasLimit: currentState.gasLimit, + // gasPrice: currentState.gasPrice, + // }); + // txAmount = '0x00'; + // txAddress = token.address; + // } + + + + // console.warn('NONCE', nonce, account.nonce, pendingNonce); + + // const txData = { + // address_n, + // // from: currentAddress.address + // to: txAddress, + // value: txAmount, + // data, + // chainId: web3.chainId, + // nonce: w3.toHex(nonce), + // gasLimit: w3.toHex(currentState.gasLimit), + // gasPrice: w3.toHex(EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei')), + // r: '', + // s: '', + // v: '', + // }; const selected: ?TrezorDevice = getState().wallet.selectedDevice; if (!selected) return; @@ -861,9 +867,17 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge txData.v = signedTransaction.payload.v; try { - const tx = new EthereumjsTx(txData); - const serializedTx = `0x${tx.serialize().toString('hex')}`; - const txid: string = await pushTx(w3, serializedTx); + const serializedTx: string = await dispatch( serializeEthereumTx(txData) ); + const push = await TrezorConnect.pushTransaction({ + tx: serializedTx, + coin: network.network + }); + + if (!push.success) { + throw new Error( push.payload.error ); + } + + const txid = push.payload.txid; dispatch({ type: SEND.TX_COMPLETE, @@ -871,7 +885,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge selectedCurrency: currentState.currency, amount: currentState.amount, total: currentState.total, - tx, + tx: txData, nonce, txid, txData, diff --git a/src/actions/TokenActions.js b/src/actions/TokenActions.js index bac9ce9d..68860968 100644 --- a/src/actions/TokenActions.js +++ b/src/actions/TokenActions.js @@ -9,7 +9,7 @@ import type { import type { State, Token } from 'reducers/TokensReducer'; import type { Account } from 'reducers/AccountsReducer'; import type { NetworkToken } from 'reducers/LocalStorageReducer'; -import { getTokenInfoAsync, getTokenBalanceAsync } from './Web3Actions'; +import * as BlockchainActions from './BlockchainActions'; export type TokenAction = { type: typeof TOKEN.FROM_STORAGE, @@ -42,15 +42,11 @@ export const load = (input: string, network: string): AsyncAction => async (disp // when options is a large list (>200 items) return result.slice(0, 100); } - const web3instance = getState().web3.find(w3 => w3.network === network); - if (!web3instance) return; - const info = await getTokenInfoAsync(web3instance.erc20, input); + const info = await dispatch( BlockchainActions.getTokenInfo(input, network) ); if (info) { return [info]; } - //await resolveAfter(300000); - //await resolveAfter(3000); }; export const setBalance = (tokenAddress: string, ethAddress: string, balance: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { @@ -68,9 +64,6 @@ export const setBalance = (tokenAddress: string, ethAddress: string, balance: st }; export const add = (token: NetworkToken, account: Account): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.find(w3 => w3.network === account.network); - if (!web3instance) return; - const tkn: Token = { loaded: false, deviceState: account.deviceState, @@ -88,7 +81,7 @@ export const add = (token: NetworkToken, account: Account): AsyncAction => async payload: tkn, }); - const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, tkn); + const tokenBalance = await dispatch( BlockchainActions.getTokenBalance(tkn) ); dispatch(setBalance(token.address, account.address, tokenBalance)); }; diff --git a/src/actions/TrezorConnectActions.js b/src/actions/TrezorConnectActions.js index f4b9925e..e110eda8 100644 --- a/src/actions/TrezorConnectActions.js +++ b/src/actions/TrezorConnectActions.js @@ -1,6 +1,6 @@ /* @flow */ import TrezorConnect, { - DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, + UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT } from 'trezor-connect'; import * as CONNECT from 'actions/constants/TrezorConnect'; import * as NOTIFICATION from 'actions/constants/notification'; @@ -12,11 +12,13 @@ import { push } from 'react-router-redux'; import type { DeviceMessage, + DeviceMessageType, UiMessage, + UiMessageType, TransportMessage, - DeviceMessageType, TransportMessageType, - UiMessageType, + BlockchainMessage, + BlockchainMessageType, } from 'trezor-connect'; import type { @@ -115,6 +117,15 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS }); }); + TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainMessage): void => { + // post event to reducers + const type: BlockchainMessageType = event.type; // assert flow type + dispatch({ + type, + payload: event.payload, + }); + }); + // $FlowIssue LOCAL not declared // window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/'; // window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/'; diff --git a/src/actions/Web3Actions.js b/src/actions/Web3Actions.js index e72ecabb..73623539 100644 --- a/src/actions/Web3Actions.js +++ b/src/actions/Web3Actions.js @@ -1,13 +1,15 @@ /* @flow */ import Web3 from 'web3'; - -import type { - ContractFactory, - EstimateGasOptions, - TransactionStatus, - TransactionReceipt, -} from 'web3'; -import type BigNumber from 'bignumber.js'; +import HDKey from 'hdkey'; +import BigNumber from 'bignumber.js'; + +import EthereumjsUtil from 'ethereumjs-util'; +import EthereumjsUnits from 'ethereumjs-units'; +import EthereumjsTx from 'ethereumjs-tx'; +import InputDataDecoder from 'ethereum-input-data-decoder'; +import TrezorConnect from 'trezor-connect'; +import type { EstimateGasOptions, TransactionStatus, TransactionReceipt } from 'web3'; +import { strip } from 'utils/ethUtils'; import * as WEB3 from 'actions/constants/web3'; import * as PENDING from 'actions/constants/pendingTx'; @@ -15,6 +17,10 @@ import type { Dispatch, GetState, AsyncAction, + PromiseAction, + AccountDiscovery, + EthereumTxRequest, + EthereumPreparedTx } from 'flowtype'; import type { Account } from 'reducers/AccountsReducer'; @@ -44,377 +50,335 @@ export type Web3Action = { } | { type: typeof WEB3.CREATE, instance: Web3Instance -} - | Web3UpdateBlockAction - | Web3UpdateGasPriceAction; +} | Web3UpdateBlockAction + | Web3UpdateGasPriceAction; + +export const initWeb3 = (network: string, urlIndex: number = 0): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + return new Promise(async (resolve, reject) => { + // check if requested web was initialized before + const instance = getState().web3.find(w3 => w3.network === network); + if (instance && instance.web3.currentProvider.connected) { + resolve(instance); + return; + } -export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { + // requested web3 wasn't initialized or is disconnected + // initialize again const { config, ERC20Abi } = getState().localStorage; - - const coin = config.coins[coinIndex]; + const coin = config.coins.find(c => c.network === network); if (!coin) { - // all instances done - dispatch({ - type: WEB3.READY, - }); + // coin not found + reject(new Error(`Network ${ network} not found in application config.`)); return; } - const { network } = coin; - const urls = coin.backends[0].urls; - - let web3host: string = urls[0]; - - if (instance) { - const currentHost = instance.currentProvider.host; - const currentHostIndex: number = urls.indexOf(currentHost); - - if (currentHostIndex + 1 < urls.length) { - web3host = urls[currentHostIndex + 1]; - } else { - console.error(`TODO: Backend ${network} not working`, instance.currentProvider); - - dispatch({ - type: WEB3.CREATE, - instance: { - network, - web3: instance, - chainId: coin.chainId, - erc20: instance.eth.contract(ERC20Abi), - latestBlock: '0', - gasPrice: '0', - }, - }); - - // try next coin - dispatch(init(null, coinIndex + 1)); - return; - } + // get first url + const url = coin.web3[ urlIndex ]; + if (!url) { + reject(new Error('Web3 backend is not responding')); + return; } - //const instance = new Web3(window.web3.currentProvider); - const web3 = new Web3(new Web3.providers.HttpProvider(web3host)); - - // instance = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') ); // UBQ - //instance = new Web3( new Web3.providers.HttpProvider('https://node.expanse.tech/') ); // EXP - //instance = new Web3( new Web3.providers.HttpProvider('http://10.34.0.91:8545/') ); - - //web3 = new Web3(new Web3.providers.HttpProvider("https://api.myetherapi.com/rop")); - //instance = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io2/QGyVKozSUEh2YhL4s2G4")); - //web3 = new Web3( new Web3.providers.HttpProvider("ws://34.230.234.51:30303") ); - + const web3 = new Web3( new Web3.providers.WebsocketProvider(url) ); - // initial check if backend is running - if (!web3.currentProvider.isConnected()) { - // try different url - dispatch(init(web3, coinIndex)); - return; - } + const onConnect = async () => { - const erc20 = web3.eth.contract(ERC20Abi); + const latestBlock = await web3.eth.getBlockNumber(); + const gasPrice = await web3.eth.getGasPrice(); - dispatch({ - type: WEB3.CREATE, - instance: { + const instance = { network, web3, chainId: coin.chainId, - erc20, - latestBlock: '0', - gasPrice: '0', - }, - }); - - // dispatch({ - // type: WEB3.GAS_PRICE_UPDATED, - // network, - // gasPrice - // }); - - - // console.log("GET CHAIN", instance.version.network) - - // instance.version.getWhisper((err, shh) => { - // console.log("-----whisperrr", error, shh) - // }) - + erc20: new web3.eth.Contract(ERC20Abi), + latestBlock, + gasPrice, + } - // const sshFilter = instance.ssh.filter('latest'); - // sshFilter.watch((error, blockHash) => { - // console.warn("SSH", error, blockHash); - // }); + console.warn("CONNECT", web3) - //const shh = instance.shh.newIdentity(); + dispatch({ + type: WEB3.CREATE, + instance, + }); - // const latestBlockFilter = web3.eth.filter('latest'); + // await dispatch( _onNewBlock(instance) ); - const onBlockMined = async (error: ?Error, blockHash: ?string) => { - if (error) { - window.setTimeout(() => { - // try again - onBlockMined(new Error('manually_triggered_error'), undefined); - }, 30000); - } - - if (blockHash) { - dispatch({ - type: WEB3.BLOCK_UPDATED, - network, - blockHash, - }); - } + resolve(instance); + } - // TODO: filter only current device - const accounts = getState().accounts.filter(a => a.network === network); - for (const account of accounts) { - const nonce = await getNonceAsync(web3, account.address); - if (nonce !== account.nonce) { - dispatch(AccountsActions.setNonce(account.address, account.network, account.deviceState, nonce)); - - // dispatch( getBalance(account) ); - // TODO: check if nonce was updated, - // update tokens balance, - // update account balance, - // update pending transactions + const onEnd = async () => { + web3.currentProvider.removeAllListeners('connect'); + web3.currentProvider.removeAllListeners('end'); + web3.currentProvider.removeAllListeners('error'); + web3.currentProvider.reset(); + // if (web3.eth) + // web3.eth.clearSubscriptions(); + + const instance = getState().web3.find(w3 => w3.network === network); + + if (instance && instance.web3.currentProvider.connected) { + // backend disconnects + // dispatch({ + // type: 'WEB3.DISCONNECT', + // network + // }); + } else { + // backend initialization error for given url, try next one + try { + const web3 = await dispatch( initWeb3(network, urlIndex + 1) ); + resolve(web3); + } catch (error) { + reject(error); } - dispatch(getBalance(account)); - // dispatch( getNonce(account) ); } + } - const tokens = getState().tokens.filter(t => t.network === network); - tokens.forEach(token => dispatch(getTokenBalance(token))); - - dispatch(getGasPrice(network)); - - const pending = getState().pending.filter(p => p.network === network); - pending.forEach(pendingTx => dispatch(getTransactionReceipt(pendingTx))); - }; - - // latestBlockFilter.watch(onBlockMined); - onBlockMined(new Error('manually_triggered_error'), undefined); - + web3.currentProvider.on('connect', onConnect); + web3.currentProvider.on('end', onEnd); + web3.currentProvider.on('error', onEnd); + }); +} - // init next coin - dispatch(init(web3, coinIndex + 1)); +const _onNewBlock = (instance: Web3Instance): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + // const latestBlock = await instance.web3.eth.getBlockNumber(); - // let instance2 = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') ); - // console.log("INIT WEB3", instance, instance2); - // instance2.eth.getGasPrice((error, gasPrice) => { - // console.log("---gasss price from UBQ", gasPrice) - // }); - }; -} + // dispatch({ + // type: WEB3.BLOCK_UPDATED, + // network: instance.network, + // blockHash: latestBlock, + // }); + -export function getGasPrice(network: string): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const index: number = getState().web3.findIndex(w3 => w3.network === network); + // TODO: filter only current device + const accounts = getState().accounts.filter(a => a.network === instance.network); + for (const account of accounts) { + const nonce = await instance.web3.eth.getTransactionCount(account.address); + if (nonce !== account.nonce) { + dispatch(AccountsActions.setNonce(account.address, account.network, account.deviceState, nonce)); + } - const web3instance = getState().web3[index]; - const { web3 } = web3instance; - web3.eth.getGasPrice((error, gasPrice) => { - if (!error) { - if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) { - dispatch({ - type: WEB3.GAS_PRICE_UPDATED, - network, - gasPrice, - }); - } - } - }); - }; -} + const balance = await instance.web3.eth.getBalance(account.address); + const newBalance = EthereumjsUnits.convert(balance, 'wei', 'ether'); + if (newBalance !== account.balance) { + dispatch(AccountsActions.setBalance( + account.address, + account.network, + account.deviceState, + newBalance + )); + } + } + + const tokens = getState().tokens.filter(t => t.network === instance.network); + for (const token of tokens) { + const balance = await dispatch( getTokenBalance(token) ); + console.warn("TOK BALAC", balance) + // const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10); + if (balance !== token.balance) { + dispatch(TokenActions.setBalance( + token.address, + token.ethAddress, + balance, + )); + } + } -export function getBalance(account: Account): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0]; - const { web3 } = web3instance; - - web3.eth.getBalance(account.address, (error: Error, balance: BigNumber) => { - if (!error) { - const newBalance: string = web3.fromWei(balance.toString(), 'ether'); - if (account.balance !== newBalance) { - dispatch(AccountsActions.setBalance( - account.address, - account.network, - account.deviceState, - newBalance, - )); - - // dispatch( loadHistory(addr) ); - } - } - }); - }; -} + // dispatch(getGasPrice(network)); -export function getTokenBalance(token: Token): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.filter(w3 => w3.network === token.network)[0]; - const contract = web3instance.erc20.at(token.address); - - contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => { - if (balance) { - const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10); - if (newBalance !== token.balance) { - dispatch(TokenActions.setBalance( - token.address, - token.ethAddress, - newBalance, - )); - } - } - }); - }; + } -export function getNonce(account: Account): AsyncAction { - return async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0]; - const { web3 } = web3instance; - - web3.eth.getTransactionCount(account.address, (error: Error, result: number) => { - if (!error) { - if (account.nonce !== result) { - dispatch(AccountsActions.setNonce(account.address, account.network, account.deviceState, result)); - } - } - }); +export const discoverAccount = (address: string, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance: Web3Instance = await dispatch( initWeb3(network) ); + const balance = await instance.web3.eth.getBalance(address); + const nonce = await instance.web3.eth.getTransactionCount(address); + return { + transactions: 0, + block: 0, + balance: EthereumjsUnits.convert(balance, 'wei', 'ether'), + nonce }; } -export const getTransactionReceipt = (tx: PendingTx): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const web3instance = getState().web3.filter(w3 => w3.network === tx.network)[0]; - const { web3 } = web3instance; - - web3.eth.getTransaction(tx.id, (error: Error, status: TransactionStatus) => { - if (!error && !status) { +export const resolvePendingTransactions = (network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance: Web3Instance = await dispatch( initWeb3(network) ); + const pending = getState().pending.filter(p => p.network === network); + for (const tx of pending) { + const status = await instance.web3.eth.getTransaction(tx.id); + if (!status) { dispatch({ type: PENDING.TX_NOT_FOUND, tx, }); - } else if (status && status.blockNumber) { - web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => { - if (receipt) { - if (status.gas !== receipt.gasUsed) { - dispatch({ - type: PENDING.TX_TOKEN_ERROR, - tx, - }); - } + } else { + const receipt = await instance.web3.eth.getTransactionReceipt(tx.id); + if (receipt) { + if (status.gas !== receipt.gasUsed) { dispatch({ - type: PENDING.TX_RESOLVED, + type: PENDING.TX_TOKEN_ERROR, tx, - receipt, }); } - }); + dispatch({ + type: PENDING.TX_RESOLVED, + tx, + receipt, + }); + } } - }); -}; + } +} -export const getTransaction = (web3: Web3, txid: string): Promise => new Promise((resolve, reject) => { - web3.eth.getTransaction(txid, (error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); -}); +export const getPendingInfo = (network: string, txid: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance: Web3Instance = await dispatch( initWeb3(network) ); + const tx = await instance.web3.eth.getTransaction(txid); -export const getBalanceAsync = (web3: Web3, address: string): Promise => new Promise((resolve, reject) => { - web3.eth.getBalance(address, (error: Error, result: BigNumber) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); -}); + /* + if (tx.input !== "0x") { + // find token: + // tx.to <= smart contract address -export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise => new Promise((resolve, reject) => { - const contract = erc20.at(token.address); - contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => { - if (error) { - reject(error); - } else { - const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10); - resolve(newBalance); + // smart contract data + const decoder = new InputDataDecoder(instance.erc20.options.jsonInterface); + const data = decoder.decodeData(tx.input); + if (data.name === 'transfer') { + console.warn("DATA!", data.inputs[0], data.inputs[1].toString(10)); } - }); -}); + -export const getNonceAsync = (web3: Web3, address: string): Promise => new Promise((resolve, reject) => { - web3.eth.getTransactionCount(address, (error: Error, result: number) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); -}); + } + */ + // return tx; +} +export const getTxInput = (): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + + const instance: Web3Instance = await dispatch( initWeb3("ropsten") ); + console.warn("GETTX", instance.erc20.options.jsonInterface) + // const inputData = instance.web3.utils.hexToAscii("0xa9059cbb00000000000000000000000073d0385f4d8e00c5e6504c6030f47bf6212736a80000000000000000000000000000000000000000000000000000000000000001"); + // console.warn("input data!", inputData); +} -export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise => new Promise((resolve) => { - const contract = erc20.at(address, (error/* , res */) => { - // console.warn("callback", error, res) - }); - const info: NetworkToken = { +export const updateAccount = (account: Account, newAccount: AccountDiscovery, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance: Web3Instance = await dispatch( initWeb3(network) ); + const balance = await instance.web3.eth.getBalance(account.address); + const nonce = await instance.web3.eth.getTransactionCount(account.address); + + dispatch( AccountsActions.update( { ...account, ...newAccount, balance: EthereumjsUnits.convert(balance, 'wei', 'ether'), nonce }) ); + // TODO update tokens for this account + +} + +export const getTokenInfo = (address: string, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance: Web3Instance = await dispatch( initWeb3(network) ); + const contract = instance.erc20.clone(); + contract.options.address = address; + + const name = await contract.methods.name().call(); + const symbol = await contract.methods.symbol().call(); + const decimals = await contract.methods.decimals().call(); + + return { address, - name: '', - symbol: '', - decimals: 0, + name, + symbol, + decimals, }; +}; - contract.name.call((error: Error, name: string) => { - if (error) { - resolve(null); - return; - } - info.name = name; +export const getTokenBalance = (token: Token): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance = await dispatch( initWeb3(token.network) ); + const contract = instance.erc20.clone(); + contract.options.address = token.address; + const balance = await contract.methods.balanceOf(token.ethAddress).call(); + return new BigNumber(balance).dividedBy(Math.pow(10, token.decimals)).toString(10); +}; - contract.symbol.call((error: Error, symbol: string) => { - if (error) { - resolve(null); - return; - } - info.symbol = symbol; +export const getCurrentGasPrice = (network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance = getState().web3.find(w3 => w3.network === network); + if (instance) { + return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei'); + } else { + return "0"; + } + + // const index: number = getState().web3.findIndex(w3 => w3.network === network); + + // const web3instance = getState().web3[index]; + // const web3 = web3instance.web3; + // web3.eth.getGasPrice((error, gasPrice) => { + // if (!error) { + // if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) { + // dispatch({ + // type: WEB3.GAS_PRICE_UPDATED, + // network, + // gasPrice, + // }); + // } + // } + // }); +} - contract.decimals.call((error: Error, decimals: BigNumber) => { - if (decimals) { - info.decimals = decimals.toNumber(); - resolve(info); - } else { - resolve(null); - } - }); - }); - }); -}); - -export const estimateGas = (web3: Web3, options: EstimateGasOptions): Promise => new Promise((resolve, reject) => { - web3.eth.estimateGas(options, (error: ?Error, gas: ?number) => { - if (error) { - reject(error); - } else if (typeof gas === 'number') { - resolve(gas); - } - }); -}); +export const estimateGasLimit = (network: string, options: EstimateGasOptions): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const instance = await dispatch( initWeb3(network) ); + // TODO: allow data starting with 0x ... + options.to = '0x0000000000000000000000000000000000000000'; + options.data = `0x${options.data.length % 2 === 0 ? options.data : `0${options.data}`}`; + options.value = instance.web3.utils.toHex( EthereumjsUnits.convert(options.value || '0', 'ether', 'wei') ); + options.gasPrice = instance.web3.utils.toHex( EthereumjsUnits.convert(options.gasPrice, 'gwei', 'wei') ); -export const pushTx = (web3: Web3, tx: any): Promise => new Promise((resolve, reject) => { - web3.eth.sendRawTransaction(tx, (error: Error, result: string) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); -}); \ No newline at end of file + const limit = await instance.web3.eth.estimateGas(options); + return limit; +}; + +// export const prepareTx = (tx: EthereumTxRequest): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + +// const instance = await dispatch( initWeb3(tx.network) ); +// const token = tx.token; +// let data: string = `0x${tx.data}`; // TODO: check if already prefixed +// let value: string = instance.web3.utils.toHex( EthereumjsUnits.convert(tx.amount, 'ether', 'wei') ); +// let to: string = tx.to; + +// if (token) { +// // smart contract transaction +// const contract = instance.erc20.clone(); +// contract.options.address = token.address; +// const tokenAmount: string = new BigNumber(tx.amount).times(Math.pow(10, token.decimals)).toString(10); +// data = instance.erc20.methods.transfer(to, tokenAmount).encodeABI(); +// value = '0x00'; +// to = token.address; +// } + +// return { +// to, +// value, +// data, +// chainId: instance.chainId, +// nonce: instance.web3.utils.toHex(tx.nonce), +// gasLimit: instance.web3.utils.toHex(tx.gasLimit), +// gasPrice: instance.web3.utils.toHex( EthereumjsUnits.convert(tx.gasPrice, 'gwei', 'wei') ), +// r: '', +// s: '', +// v: '', +// } +// }; + +// export const pushTx = (network: string, tx: EthereumPreparedTx): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { +// const instance = await dispatch( initWeb3(network) ); +// const ethTx = new EthereumjsTx(tx); +// const serializedTx = `0x${ethTx.serialize().toString('hex')}`; + +// return new Promise((resolve, reject) => { +// instance.web3.eth.sendSignedTransaction(serializedTx) +// .on('error', error => reject(error)) +// .once('transactionHash', (hash: string) => { +// resolve(hash); +// }); +// }); +// }; diff --git a/src/actions/constants/account.js b/src/actions/constants/account.js index 18075a00..d1baa199 100644 --- a/src/actions/constants/account.js +++ b/src/actions/constants/account.js @@ -6,6 +6,7 @@ export const DISPOSE: 'account__dispose' = 'account__dispose'; export const CREATE: 'account__create' = 'account__create'; export const REMOVE: 'account__remove' = 'account__remove'; +export const UPDATE: 'account__update' = 'account__update'; export const SET_BALANCE: 'account__set_balance' = 'account__set_balance'; export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce'; export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage'; diff --git a/src/actions/constants/pendingTx.js b/src/actions/constants/pendingTx.js index c2cfc0c2..04f8ef91 100644 --- a/src/actions/constants/pendingTx.js +++ b/src/actions/constants/pendingTx.js @@ -2,6 +2,7 @@ export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage'; +export const ADD: 'pending__add' = 'pending__add'; export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved'; export const TX_NOT_FOUND: 'pending__tx_not_found' = 'pending__tx_not_found'; export const TX_TOKEN_ERROR: 'pending__tx_token_error' = 'pending__tx_token_error'; \ No newline at end of file diff --git a/src/services/TrezorConnectService.js b/src/services/TrezorConnectService.js index 38c9e733..a320cfd8 100644 --- a/src/services/TrezorConnectService.js +++ b/src/services/TrezorConnectService.js @@ -1,11 +1,12 @@ /* @flow */ import { push } from 'react-router-redux'; -import { - TRANSPORT, DEVICE, +import TrezorConnect, { + TRANSPORT, DEVICE_EVENT, UI_EVENT, UI, DEVICE, BLOCKCHAIN } from 'trezor-connect'; import * as TrezorConnectActions from 'actions/TrezorConnectActions'; import * as DiscoveryActions from 'actions/DiscoveryActions'; +import * as BlockchainActions from 'actions/BlockchainActions'; import * as ModalActions from 'actions/ModalActions'; import * as STORAGE from 'actions/constants/localStorage'; import * as CONNECT from 'actions/constants/TrezorConnect'; @@ -32,6 +33,9 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa // api.dispatch( push('/') ); } else if (action.type === TRANSPORT.START) { api.dispatch(TrezorConnectActions.postInit()); + + api.dispatch( BlockchainActions.subscribe('ropsten') ); + } else if (action.type === DEVICE.DISCONNECT) { api.dispatch(TrezorConnectActions.deviceDisconnect(action.device)); } else if (action.type === CONNECT.REMEMBER_REQUEST) { @@ -59,6 +63,10 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa api.dispatch(TrezorConnectActions.onSelectDevice(action.device)); } else if (action.type === CONNECT.COIN_CHANGED) { api.dispatch(TrezorConnectActions.coinChanged(action.payload.network)); + } else if (action.type === BLOCKCHAIN.BLOCK) { + // api.dispatch(BlockchainActions.onBlockMined(action.payload.coin)); + } else if (action.type === BLOCKCHAIN.NOTIFICATION) { + // api.dispatch(BlockchainActions.onNotification(action.payload)); } return action;