diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js new file mode 100644 index 00000000..72f9c111 --- /dev/null +++ b/src/actions/BlockchainActions.js @@ -0,0 +1,181 @@ +/* @flow */ + +import Web3 from 'web3'; +import HDKey from 'hdkey'; + +import EthereumjsUtil from 'ethereumjs-util'; +import EthereumjsUnits from 'ethereumjs-units'; +import EthereumjsTx from 'ethereumjs-tx'; +import TrezorConnect from 'trezor-connect'; +import BigNumber from 'bignumber.js'; + +import type { EstimateGasOptions } from 'web3'; +import type { TransactionStatus, TransactionReceipt } from 'web3'; +import { strip } from 'utils/ethUtils'; +import * as BLOCKCHAIN from 'actions/constants/blockchain'; +import * as WEB3 from 'actions/constants/web3'; +import * as PENDING from 'actions/constants/pendingTx'; + +import * as Web3Actions from './Web3Actions'; + +import type { + TrezorDevice, + Dispatch, + GetState, + Action, + AsyncAction, + PromiseAction, + ThunkAction, + AccountDiscovery, + EthereumTxRequest +} from 'flowtype'; + +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, +}; + +export const discoverAccount = (device: TrezorDevice, xpub: string, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + // get data from connect + // Temporary disabled, enable after trezor-connect@5.0.32 release + const txs = await TrezorConnect.ethereumGetAccountInfo({ + account: { + address: xpub, + // block: 3984156, + block: 0, + transactions: 0 + }, + coin: network, + }); + + if (!txs.success) { + throw new Error(txs.payload.error); + } + + // blockbook web3 fallback + const web3account = await dispatch( Web3Actions.discoverAccount(xpub, network) ); + // return { transactions: txs.payload, ...web3account }; + return { + transactions: txs.payload.transactions, + block: txs.payload.block, + balance: web3account.balance, + nonce: web3account.nonce, + }; +}; + +export const getTokenInfo = (input: string, network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + return await dispatch( Web3Actions.getTokenInfo(input, network) ); +} + +export const getTokenBalance = (token: Token): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + return await dispatch( Web3Actions.getTokenBalance(token) ); +} + +export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + try { + const gasPrice = await dispatch( Web3Actions.getCurrentGasPrice(network) ); + return new BigNumber(gasPrice); + } catch (error) { + return new BigNumber(defaultGasPrice); + } +} + +export const estimateGasLimit = (network: string, data: string, value: string, gasPrice: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + return await dispatch( Web3Actions.estimateGasLimit(network, { to: '', data, value, gasPrice }) ); +} + +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 + // } + // }); + 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 { + response.payload.forEach((a, i) => { + if (a.transactions > 0) { + 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 => { + + // this event can be triggered multiple times + // 1. check if pair [txid + address] is already in reducer + const address: string = EthereumjsUtil.toChecksumAddress(payload.tx.address); + const txInfo = await dispatch( Web3Actions.getPendingInfo(payload.coin, payload.tx.txid) ); + + // 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: { + id: payload.tx.txid, + network: payload.coin, + currency: "tETH", + amount: txInfo.value, + total: "0", + tx: {}, + nonce: txInfo.nonce, + address, + } + }); + } else { + // tx info not found (yet?) + // dispatch({ + // type: PENDING.ADD_UNKNOWN, + // payload: { + // network: payload.coin, + // ...payload.tx, + // } + // }); + } + } + + console.warn("WEB3", payload, exists, getState().pending, address) +} + + +export const subscribe = (network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const addresses: Array = getState().accounts.filter(a => a.network === network).map(a => a.address); + // $FlowIssue: trezor-connect@5.0.32 + return await TrezorConnect.blockchainSubscribe({ + // accounts: addresses, + accounts: [], + coin: network + }); +} \ No newline at end of file diff --git a/src/actions/constants/blockchain.js b/src/actions/constants/blockchain.js new file mode 100644 index 00000000..f0addc68 --- /dev/null +++ b/src/actions/constants/blockchain.js @@ -0,0 +1,7 @@ +/* @flow */ + +export const START: 'backend__start' = 'backend__start'; +export const STOP: 'backend__stop' = 'backend__stop'; +export const CONNECTING: 'backend__connecting' = 'backend__connecting'; +export const CONNECTED: 'backend__connected' = 'backend__connected'; +export const DISCONNECTED: 'backend__disconnected' = 'backend__disconnected'; \ No newline at end of file diff --git a/src/reducers/BlockchainReducer.js b/src/reducers/BlockchainReducer.js new file mode 100644 index 00000000..92de9c27 --- /dev/null +++ b/src/reducers/BlockchainReducer.js @@ -0,0 +1,62 @@ +/* @flow */ + +import { BLOCKCHAIN } from 'trezor-connect'; + +import type { Action } from 'flowtype'; + +export type BlockchainNetwork = { + +name: string; + connected: boolean; +} + +export type State = Array; + +export const initialState: State = []; + +const find = (state: State, name: string): number => { + return state.findIndex(b => b.name === name); +} + +const connect = (state: State, action: any): State => { + const network: BlockchainNetwork = { + name: action.payload.coin, + connected: true, + } + const newState: State = [...state]; + const index: number = find(newState, action.payload.coin); + if (index >= 0) { + newState[index] = network; + } else { + newState.push(network); + } + return newState; +}; + +const disconnect = (state: State, action: any): State => { + const network: BlockchainNetwork = { + name: action.payload.coin, + connected: false, + } + const newState: State = [...state]; + const index: number = find(newState, action.payload.coin); + if (index >= 0) { + newState[index] = network; + } else { + newState.push(network); + } + return newState; +}; + + +export default (state: State = initialState, action: Action): State => { + switch (action.type) { + + case BLOCKCHAIN.CONNECT: + return connect(state, action); + case BLOCKCHAIN.ERROR: + return disconnect(state, action); + + default: + return state; + } +}; \ No newline at end of file diff --git a/src/reducers/index.js b/src/reducers/index.js index 1d0be6a7..ed6f1aa6 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -19,6 +19,7 @@ import pending from 'reducers/PendingTxReducer'; import fiat from 'reducers/FiatRateReducer'; import wallet from 'reducers/WalletReducer'; import devices from 'reducers/DevicesReducer'; +import blockchain from 'reducers/BlockchainReducer'; const reducers = { router: routerReducer, @@ -39,6 +40,7 @@ const reducers = { fiat, wallet, devices, + blockchain }; export type Reducers = typeof reducers;