From 1379a2e7450d25f5b029c448dc340b8bdafd0dec Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 13 Sep 2018 14:40:41 +0200 Subject: [PATCH] update actions and reducers --- src/actions/BlockchainActions.js | 64 ++++++++++++-------- src/actions/DiscoveryActions.js | 23 +++++-- src/actions/TrezorConnectActions.js | 3 - src/actions/Web3Actions.js | 90 +++++++++++----------------- src/reducers/DiscoveryReducer.js | 17 +++--- src/reducers/PendingTxReducer.js | 41 ++++++------- src/reducers/Web3Reducer.js | 9 +++ src/services/TrezorConnectService.js | 10 ++-- 8 files changed, 136 insertions(+), 121 deletions(-) diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 3e63144f..9e4a79a3 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -34,12 +34,8 @@ import type { Token } from 'reducers/TokensReducer'; import type { NetworkToken } from 'reducers/LocalStorageReducer'; export type BlockchainAction = { - type: typeof BLOCKCHAIN.START | typeof BLOCKCHAIN.CONNECTING, - network: string, -} | { - type: typeof BLOCKCHAIN.STOP, - network: string, -}; + type: typeof BLOCKCHAIN.READY, +} export const discoverAccount = (device: TrezorDevice, xpub: string, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { // get data from connect @@ -93,36 +89,22 @@ export const estimateGasLimit = (network: string, data: string, value: string, g export const onBlockMined = (network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { // try to resolve pending transactions - // await dispatch( Web3Actions.resolvePendingTransactions(network) ); - - // get network accounts - // const accounts: Array = getState().accounts.filter(a => a.network === network).map(a => { - // return { - // address: a.address, - // block: a.block, - // transactions: a.transactions - // } - // }); + await dispatch( Web3Actions.resolvePendingTransactions(network) ); + const accounts: Array = getState().accounts.filter(a => a.network === network); - // find out which account changed const response = await TrezorConnect.ethereumGetAccountInfo({ accounts, coin: network, }); - if (!response.success) { - - } else { + if (response.success) { response.payload.forEach((a, i) => { if (a.transactions > 0) { - // dispatch( Web3Actions.updateAccount(accounts[i], a, network) ) + dispatch( Web3Actions.updateAccount(accounts[i], a, network) ) } }); } - - //return await dispatch( Web3Actions.estimateGasLimit(network, { to: '', data, value, gasPrice }) ); - console.warn("onBlockMined", response) } export const onNotification = (payload: any): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { @@ -177,4 +159,38 @@ export const subscribe = (network: string): PromiseAction => async (dispat accounts: [], coin: network }); +} + +// Conditionally subscribe to blockchain backend +// called after TrezorConnect.init successfully emits TRANSPORT.START event +// checks if there are discovery processes loaded from LocalStorage +// if so starts subscription to proper networks +export const init = (): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + if (getState().discovery.length > 0) { + // get unique networks + const networks: Array = []; + getState().discovery.forEach(discovery => { + if (networks.indexOf(discovery.network) < 0) { + networks.push(discovery.network); + } + }); + + // subscribe + for (let i = 0; i < networks.length; i++) { + await dispatch( subscribe(networks[i]) ); + } + } else { + await dispatch( subscribe('ropsten') ); + } + + // continue wallet initialization + dispatch({ + type: BLOCKCHAIN.READY + }); +} + +// Handle BLOCKCHAIN.ERROR event from TrezorConnect +// disconnect and remove Web3 webscocket instance if exists +export const error = (payload: any): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + dispatch( Web3Actions.disconnect(payload.coin) ); } \ No newline at end of file diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index 58be9d91..107a061c 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -6,7 +6,7 @@ import * as DISCOVERY from 'actions/constants/discovery'; import * as ACCOUNT from 'actions/constants/account'; import * as NOTIFICATION from 'actions/constants/notification'; import type { - ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice, + ThunkAction, AsyncAction, PromiseAction, Action, GetState, Dispatch, TrezorDevice, } from 'flowtype'; import type { Discovery, State } from 'reducers/DiscoveryReducer'; import * as AccountsActions from './AccountsActions'; @@ -26,7 +26,7 @@ export type DiscoveryStartAction = { } export type DiscoveryWaitingAction = { - type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BACKEND, + type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN, device: TrezorDevice, network: string } @@ -80,8 +80,12 @@ export const start = (device: TrezorDevice, network: string, ignoreCompleted?: b } const blockchain = getState().blockchain.find(b => b.name === network); - if (blockchain && !blockchain.connected) { - console.error("NO BACKEND!") // TODO + if (blockchain && !blockchain.connected && (!discoveryProcess || !discoveryProcess.completed)) { + dispatch({ + type: DISCOVERY.WAITING_FOR_BLOCKCHAIN, + device, + network, + }); return; } @@ -93,7 +97,7 @@ export const start = (device: TrezorDevice, network: string, ignoreCompleted?: b device, network, }); - } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) { + } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) { // discovery cycle was interrupted // start from beginning dispatch(begin(device, network)); @@ -268,12 +272,19 @@ const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction } +export const reconnect = (network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + await dispatch(BlockchainActions.subscribe(network)); + dispatch(restore()); +} + export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { const selected = getState().wallet.selectedDevice; if (selected && selected.connected && selected.features) { - const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.waitingForDevice); + const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && (d.interrupted || d.waitingForDevice || d.waitingForBlockchain)); + console.warn("AAAA2") if (discoveryProcess) { + console.warn("AAAA3", discoveryProcess) dispatch(start(selected, discoveryProcess.network)); } } diff --git a/src/actions/TrezorConnectActions.js b/src/actions/TrezorConnectActions.js index 064b04d8..eeef3218 100644 --- a/src/actions/TrezorConnectActions.js +++ b/src/actions/TrezorConnectActions.js @@ -9,7 +9,6 @@ import { getDuplicateInstanceNumber } from 'reducers/utils'; import { push } from 'react-router-redux'; - import type { DeviceMessage, DeviceMessageType, @@ -129,8 +128,6 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS // $FlowIssue LOCAL not declared window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/'; // window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/'; - //window.__TREZOR_CONNECT_SRC = 'https://sisyfos.trezor.io/connect/'; - // window.__TREZOR_CONNECT_SRC = 'https://localhost:8088/'; try { await TrezorConnect.init({ diff --git a/src/actions/Web3Actions.js b/src/actions/Web3Actions.js index 73623539..9050cd85 100644 --- a/src/actions/Web3Actions.js +++ b/src/actions/Web3Actions.js @@ -16,6 +16,7 @@ import * as PENDING from 'actions/constants/pendingTx'; import type { Dispatch, GetState, + ThunkAction, AsyncAction, PromiseAction, AccountDiscovery, @@ -23,6 +24,7 @@ import type { EthereumPreparedTx } from 'flowtype'; +import type { EthereumAccount } from 'trezor-connect'; import type { Account } from 'reducers/AccountsReducer'; import type { PendingTx } from 'reducers/PendingTxReducer'; import type { Web3Instance } from 'reducers/Web3Reducer'; @@ -48,7 +50,7 @@ export type Web3Action = { } | { type: typeof WEB3.START, } | { - type: typeof WEB3.CREATE, + type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT, instance: Web3Instance } | Web3UpdateBlockAction | Web3UpdateGasPriceAction; @@ -108,13 +110,8 @@ export const initWeb3 = (network: string, urlIndex: number = 0): PromiseAction { - web3.currentProvider.removeAllListeners('connect'); - web3.currentProvider.removeAllListeners('end'); - web3.currentProvider.removeAllListeners('error'); - web3.currentProvider.reset(); - // if (web3.eth) - // web3.eth.clearSubscriptions(); + web3.currentProvider.reset(); const instance = getState().web3.find(w3 => w3.network === network); if (instance && instance.web3.currentProvider.connected) { @@ -263,14 +260,25 @@ export const getTxInput = (): PromiseAction => async (dispatch: Dispatch, } -export const updateAccount = (account: Account, newAccount: AccountDiscovery, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { +export const updateAccount = (account: Account, newAccount: EthereumAccount, 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 - + + // update tokens for this account + const tokens = getState().tokens.filter(t => t.network === account.network && t.ethAddress === account.address); + for (const token of tokens) { + const balance = await dispatch( getTokenBalance(token) ); + // 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 const getTokenInfo = (address: string, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { @@ -337,48 +345,18 @@ export const estimateGasLimit = (network: string, options: EstimateGasOptions): 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); -// }); -// }); -// }; +export const disconnect = (network: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + // check if Web3 was already initialized + const instance = getState().web3.find(w3 => w3.network === network); + if (instance) { + // reset current connection + instance.web3.currentProvider.reset(); + instance.web3.currentProvider.connection.close(); + + // remove instance from reducer + dispatch({ + type: WEB3.DISCONNECT, + instance + }); + } +}; diff --git a/src/reducers/DiscoveryReducer.js b/src/reducers/DiscoveryReducer.js index 244df89c..aebb392e 100644 --- a/src/reducers/DiscoveryReducer.js +++ b/src/reducers/DiscoveryReducer.js @@ -29,7 +29,7 @@ export type Discovery = { interrupted: boolean; completed: boolean; waitingForDevice: boolean; - waitingForBackend: boolean; + waitingForBlockchain: boolean; } export type State = Array; @@ -53,7 +53,7 @@ const start = (state: State, action: DiscoveryStartAction): State => { interrupted: false, completed: false, waitingForDevice: false, - waitingForBackend: false, + waitingForBlockchain: false, }; const newState: State = [...state]; @@ -96,6 +96,7 @@ const stop = (state: State, action: DiscoveryStopAction): State => { if (d.deviceState === action.device.state && !d.completed) { d.interrupted = true; d.waitingForDevice = false; + d.waitingForBlockchain = false; } return d; }); @@ -114,7 +115,7 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State = interrupted: false, completed: false, waitingForDevice: true, - waitingForBackend: false, + waitingForBlockchain: false, }; const index: number = findIndex(state, action.network, deviceState); @@ -128,7 +129,7 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State = return newState; }; -const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State => { +const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): State => { const deviceState: string = action.device.state || '0'; const instance: Discovery = { network: action.network, @@ -141,7 +142,7 @@ const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State interrupted: false, completed: false, waitingForDevice: false, - waitingForBackend: true, + waitingForBlockchain: true, }; const index: number = findIndex(state, action.network, deviceState); @@ -167,8 +168,8 @@ export default function discovery(state: State = initialState, action: Action): return complete(state, action); case DISCOVERY.WAITING_FOR_DEVICE: return waitingForDevice(state, action); - case DISCOVERY.WAITING_FOR_BACKEND: - return waitingForBackend(state, action); + case DISCOVERY.WAITING_FOR_BLOCKCHAIN: + return waitingForBlockchain(state, action); case DISCOVERY.FROM_STORAGE: return action.payload.map((d) => { const hdKey: HDKey = new HDKey(); @@ -179,7 +180,7 @@ export default function discovery(state: State = initialState, action: Action): hdKey, interrupted: false, waitingForDevice: false, - waitingForBackend: false, + waitingForBlockchain: false, }; }); case CONNECT.FORGET: diff --git a/src/reducers/PendingTxReducer.js b/src/reducers/PendingTxReducer.js index 5cccf9e8..b8c80f57 100644 --- a/src/reducers/PendingTxReducer.js +++ b/src/reducers/PendingTxReducer.js @@ -22,23 +22,24 @@ export type State = Array; const initialState: State = []; -// const add01 = (state: State, action: SendTxAction): State => { -// const newState = [...state]; -// newState.push({ -// id: action.txid, -// network: action.account.network, -// currency: action.selectedCurrency, -// amount: action.amount, -// total: action.total, -// tx: action.tx, -// nonce: action.nonce, -// address: action.account.address, -// rejected: false, -// }); -// return newState; -// }; +const add = (state: State, action: SendTxAction): State => { + const newState = [...state]; + newState.push({ + type: 'send', + id: action.txid, + network: action.account.network, + currency: action.selectedCurrency, + amount: action.amount, + total: action.total, + tx: action.tx, + nonce: action.nonce, + address: action.account.address, + rejected: false, + }); + return newState; +}; -const add = (state: State, payload: any): State => { +const add_NEW = (state: State, payload: any): State => { const newState = [...state]; newState.push(payload); return newState; @@ -55,11 +56,11 @@ const reject = (state: State, id: string): State => state.map((tx) => { export default function pending(state: State = initialState, action: Action): State { switch (action.type) { - // case SEND.TX_COMPLETE: - // return add(state, action); + case SEND.TX_COMPLETE: + return add(state, action); - case PENDING.ADD: - return add(state, action.payload); + // case PENDING.ADD: + // return add(state, action.payload); case PENDING.TX_RESOLVED: return remove(state, action.tx.id); case PENDING.TX_NOT_FOUND: diff --git a/src/reducers/Web3Reducer.js b/src/reducers/Web3Reducer.js index d0d45d23..9c25d9c8 100644 --- a/src/reducers/Web3Reducer.js +++ b/src/reducers/Web3Reducer.js @@ -51,6 +51,13 @@ const updateGasPrice = (state: State, action: Web3UpdateGasPriceAction): State = return newState; }; +const disconnect = (state: State, instance: Web3Instance): State => { + const index: number = state.indexOf(instance); + const newState: Array = [...state]; + newState.splice(index, 1); + return newState; +}; + export default function web3(state: State = initialState, action: Action): State { switch (action.type) { case WEB3.CREATE: @@ -59,6 +66,8 @@ export default function web3(state: State = initialState, action: Action): State return updateLatestBlock(state, action); case WEB3.GAS_PRICE_UPDATED: return updateGasPrice(state, action); + case WEB3.DISCONNECT: + return disconnect(state, action.instance); default: return state; } diff --git a/src/services/TrezorConnectService.js b/src/services/TrezorConnectService.js index a320cfd8..5a4255c2 100644 --- a/src/services/TrezorConnectService.js +++ b/src/services/TrezorConnectService.js @@ -10,6 +10,7 @@ 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'; +import { READY as BLOCKCHAIN_READY } from 'actions/constants/blockchain'; import type { Middleware, @@ -32,10 +33,9 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa // TODO: check if modal is open // api.dispatch( push('/') ); } else if (action.type === TRANSPORT.START) { + api.dispatch(BlockchainActions.init()); + } else if (action.type === BLOCKCHAIN_READY) { 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) { @@ -64,9 +64,11 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa } 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)); + api.dispatch(BlockchainActions.onBlockMined(action.payload.coin)); } else if (action.type === BLOCKCHAIN.NOTIFICATION) { // api.dispatch(BlockchainActions.onNotification(action.payload)); + } else if (action.type === BLOCKCHAIN.ERROR) { + api.dispatch( BlockchainActions.error(action.payload) ); } return action;