From 25130ce282c5497c9b823c13fd5a97ae4444b903 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 21 Nov 2018 13:56:32 +0100 Subject: [PATCH 01/72] add xrp to config --- public/data/appConfig.json | 20 ++++++++++++++++++ src/components/images/CoinLogo/images/xrp.png | Bin 0 -> 2840 bytes 2 files changed, 20 insertions(+) create mode 100644 src/components/images/CoinLogo/images/xrp.png diff --git a/public/data/appConfig.json b/public/data/appConfig.json index ecce7034..fc345eef 100644 --- a/public/data/appConfig.json +++ b/public/data/appConfig.json @@ -1,6 +1,20 @@ { "networks": [ { + "type": "ripple", + "name": "Ripple Testnet", + "symbol": "XRP", + "shortcut": "xrp", + "bip44": "m/44'/144'/a'/0/0", + "explorer": { + "tx": "https://etherscan.io/tx/", + "address": "https://etherscan.io/address/" + } + } + ], + "networks2": [ + { + "type": "ethereum", "name": "Ethereum", "symbol": "ETH", "shortcut": "eth", @@ -19,6 +33,7 @@ } }, { + "type": "ethereum", "name": "Ethereum Classic", "symbol": "ETC", "shortcut": "etc", @@ -37,6 +52,7 @@ } }, { + "type": "ethereum", "name": "Ethereum Ropsten", "symbol": "tROP", "shortcut": "trop", @@ -64,6 +80,10 @@ { "network": "etc", "url": "https://api.coingecko.com/api/v3/coins/ethereum-classic" + }, + { + "network": "xrp", + "url": "https://api.coingecko.com/api/v3/coins/ripple" } ], diff --git a/src/components/images/CoinLogo/images/xrp.png b/src/components/images/CoinLogo/images/xrp.png new file mode 100644 index 0000000000000000000000000000000000000000..e7f8e7024d422ed9968aa0638512f7e15d0dc0a4 GIT binary patch literal 2840 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 Date: Wed, 21 Nov 2018 13:56:58 +0100 Subject: [PATCH 02/72] exclude blockchain-link workers from webpack config --- webpack/local.babel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/local.babel.js b/webpack/local.babel.js index 3c4fe7eb..e730ec46 100644 --- a/webpack/local.babel.js +++ b/webpack/local.babel.js @@ -50,7 +50,7 @@ module.exports = { rules: [ { test: /\.js?$/, - exclude: /node_modules/, + exclude: [/node_modules/, /blockchain-link\/build\/workers/], use: ['babel-loader'], }, { From c6ce9a94d72887066dee1e972d93b5a97101f30c Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 23 Nov 2018 12:58:27 +0100 Subject: [PATCH 03/72] DiscoveryReducer: HDKey only for ethereum --- src/reducers/DiscoveryReducer.js | 45 +++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/reducers/DiscoveryReducer.js b/src/reducers/DiscoveryReducer.js index 28294b2d..093d938b 100644 --- a/src/reducers/DiscoveryReducer.js +++ b/src/reducers/DiscoveryReducer.js @@ -19,9 +19,6 @@ import type { Account } from './AccountsReducer'; export type Discovery = { network: string; - publicKey: string; - chainCode: string; - hdKey: HDKey; basePath: Array; deviceState: string; accountIndex: number; @@ -29,7 +26,11 @@ export type Discovery = { completed: boolean; waitingForDevice: boolean; waitingForBlockchain: boolean; -} + + publicKey: string; // used in ethereum only + chainCode: string; // used in ethereum only + hdKey: HDKey; // used in ethereum only +}; export type State = Array; const initialState: State = []; @@ -38,14 +39,16 @@ const findIndex = (state: State, network: string, deviceState: string): number = const start = (state: State, action: DiscoveryStartAction): State => { const deviceState: string = action.device.state || '0'; - const hdKey: HDKey = new HDKey(); - hdKey.publicKey = Buffer.from(action.publicKey, 'hex'); - hdKey.chainCode = Buffer.from(action.chainCode, 'hex'); + + let hdKey: ?HDKey; + if (action.network.type === 'ethereum') { + hdKey = new HDKey(); + hdKey.publicKey = Buffer.from(action.publicKey, 'hex'); + hdKey.chainCode = Buffer.from(action.chainCode, 'hex'); + } + const instance: Discovery = { - network: action.network, - publicKey: action.publicKey, - chainCode: action.chainCode, - hdKey, + network: action.network.shortcut, basePath: action.basePath, deviceState, accountIndex: 0, @@ -53,10 +56,14 @@ const start = (state: State, action: DiscoveryStartAction): State => { completed: false, waitingForDevice: false, waitingForBlockchain: false, + + publicKey: action.publicKey, + chainCode: action.chainCode, + hdKey, }; const newState: State = [...state]; - const index: number = findIndex(state, action.network, deviceState); + const index: number = findIndex(state, action.network.shortcut, deviceState); if (index >= 0) { newState[index] = instance; } else { @@ -107,15 +114,16 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State = const instance: Discovery = { network: action.network, deviceState, - publicKey: '', - chainCode: '', - hdKey: null, basePath: [], accountIndex: 0, interrupted: false, completed: false, waitingForDevice: true, waitingForBlockchain: false, + + publicKey: '', + chainCode: '', + hdKey: null, }; const index: number = findIndex(state, action.network, deviceState); @@ -134,15 +142,16 @@ const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): Sta const instance: Discovery = { network: action.network, deviceState, - publicKey: '', - chainCode: '', - hdKey: null, basePath: [], accountIndex: 0, interrupted: false, completed: false, waitingForDevice: false, waitingForBlockchain: true, + + publicKey: '', + chainCode: '', + hdKey: null, }; const index: number = findIndex(state, action.network, deviceState); From a58644f8ec3bc87f226a11fc6d0aa024f53c88a8 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 23 Nov 2018 13:00:03 +0100 Subject: [PATCH 04/72] Load tokens only is exists in config --- src/actions/LocalStorageActions.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/actions/LocalStorageActions.js b/src/actions/LocalStorageActions.js index 786da55c..16077553 100644 --- a/src/actions/LocalStorageActions.js +++ b/src/actions/LocalStorageActions.js @@ -156,8 +156,10 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise => // load tokens const tokens = await config.networks.reduce(async (promise: Promise, network: Network): Promise => { const collection: TokensCollection = await promise; - const json = await httpRequest(network.tokens, 'json'); - collection[network.shortcut] = json; + if (network.tokens) { + const json = await httpRequest(network.tokens, 'json'); + collection[network.shortcut] = json; + } return collection; }, Promise.resolve({})); @@ -175,7 +177,7 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise => } }; -const VERSION: string = '1'; +const VERSION: string = '2'; const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => { // validate version From 5888dca31f786e32e8477192c77f5c191af65fb9 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 23 Nov 2018 13:01:16 +0100 Subject: [PATCH 05/72] flowtype: add type to Network declaration --- src/reducers/LocalStorageReducer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reducers/LocalStorageReducer.js b/src/reducers/LocalStorageReducer.js index c9d81925..cb486f99 100644 --- a/src/reducers/LocalStorageReducer.js +++ b/src/reducers/LocalStorageReducer.js @@ -6,6 +6,7 @@ import * as STORAGE from 'actions/constants/localStorage'; import type { Action } from 'flowtype'; export type Network = { + type: string; name: string; shortcut: string; symbol: string; From 198dd4e9c712bdd963f1cc455a1a621c1b4b1d15 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 23 Nov 2018 13:07:22 +0100 Subject: [PATCH 06/72] add RippleDiscoveryActions --- src/actions/ripple/RippleDiscoveryActions.js | 306 +++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 src/actions/ripple/RippleDiscoveryActions.js diff --git a/src/actions/ripple/RippleDiscoveryActions.js b/src/actions/ripple/RippleDiscoveryActions.js new file mode 100644 index 00000000..0af09faa --- /dev/null +++ b/src/actions/ripple/RippleDiscoveryActions.js @@ -0,0 +1,306 @@ +/* @flow */ + +import TrezorConnect from 'trezor-connect'; +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, + PromiseAction, + PayloadAction, + GetState, + Dispatch, + TrezorDevice, + Network, +} from 'flowtype'; +import type { Discovery, State } from 'reducers/DiscoveryReducer'; +import * as BlockchainActions from '../BlockchainActions'; + +export type DiscoveryStartAction = { + type: typeof DISCOVERY.START, + device: TrezorDevice, + network: Network, + publicKey: string, + chainCode: string, + basePath: Array, +} + +export type DiscoveryWaitingAction = { + type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN, + device: TrezorDevice, + network: string, +} + +export type DiscoveryCompleteAction = { + type: typeof DISCOVERY.COMPLETE, + device: TrezorDevice, + network: string, +} + +export type DiscoveryAction = { + type: typeof DISCOVERY.FROM_STORAGE, + payload: State +} | { + type: typeof DISCOVERY.STOP, + device: TrezorDevice +} | DiscoveryStartAction + | DiscoveryWaitingAction + | DiscoveryCompleteAction; + +// There are multiple async methods during discovery process (trezor-connect and blockchain actions) +// This method will check after each of async action if process was interrupted (for example by network change or device disconnect) +const isProcessInterrupted = (process?: Discovery): PayloadAction => (dispatch: Dispatch, getState: GetState): boolean => { + if (!process) { + return false; + } + const { deviceState, network } = process; + const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === deviceState && d.network === network); + if (!discoveryProcess) return false; + return discoveryProcess.interrupted; +}; + +// Private action +// Called from "this.begin", "this.restore", "this.addAccount" +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 } = getState(); + 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.shortcut === network); + if (blockchain && !blockchain.connected && (!discoveryProcess || !discoveryProcess.completed)) { + dispatch({ + type: DISCOVERY.WAITING_FOR_BLOCKCHAIN, + device, + network, + }); + return; + } + + if (!discoveryProcess) { + dispatch(begin(device, network)); + } else if (discoveryProcess.completed && !ignoreCompleted) { + dispatch({ + type: DISCOVERY.COMPLETE, + device, + network, + }); + } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) { + // discovery cycle was interrupted + // start from beginning + dispatch(begin(device, network)); + } else { + dispatch(discoverAccount(device, discoveryProcess)); + } +}; + +// first iteration +// generate public key for this account +// start discovery process +const begin = (device: TrezorDevice, networkName: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { config } = getState().localStorage; + const network = config.networks.find(c => c.shortcut === networkName); + if (!network) return; + + dispatch({ + type: DISCOVERY.WAITING_FOR_DEVICE, + device, + network: networkName, + }); + + // check for interruption + // corner case: DISCOVERY.START wasn't called yet, but Discovery exists in reducer created by DISCOVERY.WAITING_FOR_DEVICE action + // this is why we need to get process instance directly from reducer + const discoveryProcess = getState().discovery.find(d => d.deviceState === device.state && d.network === network); + if (dispatch(isProcessInterrupted(discoveryProcess))) return; + + //const basePath: Array = response.payload.path; + + // send data to reducer + dispatch({ + type: DISCOVERY.START, + network, + device, + publicKey: '', + chainCode: '', + basePath: [0, 0, 0], + }); + + // next iteration + dispatch(start(device, networkName)); +}; + + +const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { config } = getState().localStorage; + const network = config.networks.find(c => c.shortcut === discoveryProcess.network); + if (!network) return; + + const { completed, accountIndex } = discoveryProcess; + const path = network.bip44.slice(0).replace('a', accountIndex.toString()); + + // $FlowIssue npm not released yet + const response = await TrezorConnect.rippleGetAccountInfo({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + account: { + path, + block: 0, + transactions: 0, + }, + keepSession: true, // acquire and hold session + useEmptyPassphrase: device.useEmptyPassphrase, + }); + + if (!response.success) { + dispatch({ + type: DISCOVERY.STOP, + device, + }); + + dispatch({ + type: NOTIFICATION.ADD, + payload: { + type: 'error', + title: 'Account discovery error', + message: response.payload.error, + cancelable: true, + actions: [ + { + label: 'Try again', + callback: () => { + dispatch(start(device, discoveryProcess.network)); + }, + }, + ], + }, + }); + return; + } + + if (dispatch(isProcessInterrupted(discoveryProcess))) return; + + const account = response.payload; + const accountIsEmpty = account.sequence <= 0 && account.balance === '0'; + if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && discoveryProcess.accountIndex === 0)) { + dispatch({ + type: ACCOUNT.CREATE, + payload: { + index: discoveryProcess.accountIndex, + loaded: true, + network: network.shortcut, + deviceID: device.features ? device.features.device_id : '0', + deviceState: device.state || '0', + addressPath: account.path, + address: account.address, + balance: account.balance, + nonce: account.sequence, + block: account.block, + transactions: account.transactions, + }, + }); + } + + if (accountIsEmpty) { + dispatch(finish(device, discoveryProcess)); + } else if (!completed) { + dispatch(discoverAccount(device, discoveryProcess)); + } +}; + +const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch): Promise => { + await TrezorConnect.getFeatures({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + keepSession: false, + // useEmptyPassphrase: !device.instance, + useEmptyPassphrase: device.useEmptyPassphrase, + }); + + // await dispatch(BlockchainActions.subscribe(discoveryProcess.network)); + + if (dispatch(isProcessInterrupted(discoveryProcess))) return; + + dispatch({ + type: DISCOVERY.COMPLETE, + device, + network: discoveryProcess.network, + }); +}; + +export const reconnect = (network: string): PromiseAction => async (dispatch: Dispatch): Promise => { + await dispatch(BlockchainActions.subscribe(network)); + dispatch(restore()); +}; + +// Called after DEVICE.CONNECT ('trezor-connect') or CONNECT.AUTH_DEVICE actions in WalletService +// OR after BlockchainSubscribe in this.reconnect +export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + // check if current url has "network" parameter + const urlParams = getState().router.location.state; + if (!urlParams.network) return; + + // make sure that "selectedDevice" exists + const selected = getState().wallet.selectedDevice; + if (!selected) return; + + // find discovery process for requested network + const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.network === urlParams.network); + + // if there was no process befor OR process was interrupted/waiting + const shouldStart = !discoveryProcess || (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain); + if (shouldStart) { + dispatch(start(selected, urlParams.network)); + } +}; + +export const stop = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const device: ?TrezorDevice = getState().wallet.selectedDevice; + if (!device) return; + + // get all uncompleted discovery processes which assigned to selected device + const discoveryProcesses = getState().discovery.filter(d => d.deviceState === device.state && !d.completed); + if (discoveryProcesses.length > 0) { + dispatch({ + type: DISCOVERY.STOP, + device, + }); + } +}; + +export const addAccount = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { + const selected = getState().wallet.selectedDevice; + if (!selected) return; + dispatch(start(selected, getState().router.location.state.network, true)); +}; From 0918403024b7e9ee5b5dde63a043be5594ac729d Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Fri, 23 Nov 2018 14:46:24 +0100 Subject: [PATCH 07/72] split discovery actions to coin specific files --- src/actions/DiscoveryActions.js | 135 ++++---- .../ethereum/EthereumDiscoveryActions.js | 90 ++++++ src/actions/ripple/RippleDiscoveryActions.js | 290 ++---------------- src/reducers/AccountsReducer.js | 1 + 4 files changed, 175 insertions(+), 341 deletions(-) create mode 100644 src/actions/ethereum/EthereumDiscoveryActions.js diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index 3c497f2e..57330eda 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -1,7 +1,6 @@ /* @flow */ import TrezorConnect from 'trezor-connect'; -import EthereumjsUtil from 'ethereumjs-util'; import * as DISCOVERY from 'actions/constants/discovery'; import * as ACCOUNT from 'actions/constants/account'; import * as NOTIFICATION from 'actions/constants/notification'; @@ -14,29 +13,25 @@ import type { GetState, Dispatch, TrezorDevice, + Account, } from 'flowtype'; import type { Discovery, State } from 'reducers/DiscoveryReducer'; import * as BlockchainActions from './BlockchainActions'; +import * as EthereumDiscoveryActions from './ethereum/EthereumDiscoveryActions'; +import * as RippleDiscoveryActions from './ripple/RippleDiscoveryActions'; -export type DiscoveryStartAction = { - type: typeof DISCOVERY.START, - device: TrezorDevice, - network: string, - publicKey: string, - chainCode: string, - basePath: Array, -} +export type DiscoveryStartAction = EthereumDiscoveryActions.DiscoveryStartAction | RippleDiscoveryActions.DiscoveryStartAction; export type DiscoveryWaitingAction = { type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN, device: TrezorDevice, - network: string + network: string, } export type DiscoveryCompleteAction = { type: typeof DISCOVERY.COMPLETE, device: TrezorDevice, - network: string + network: string, } export type DiscoveryAction = { @@ -122,44 +117,40 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean) // 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 begin = (device: TrezorDevice, networkName: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { const { config } = getState().localStorage; - const networkData = config.networks.find(c => c.shortcut === network); - if (!networkData) return; + const network = config.networks.find(c => c.shortcut === networkName); + if (!network) return; dispatch({ type: DISCOVERY.WAITING_FOR_DEVICE, device, - network, + network: networkName, }); - // get xpub from TREZOR - const response = await TrezorConnect.getPublicKey({ - device: { - path: device.path, - instance: device.instance, - state: device.state, - }, - path: networkData.bip44, - keepSession: true, // acquire and hold session - //useEmptyPassphrase: !device.instance, - useEmptyPassphrase: device.useEmptyPassphrase, - }); + let startAction: DiscoveryStartAction; - // handle TREZOR response error - if (!response.success) { + try { + if (network.type === 'ethereum') { + startAction = await dispatch(EthereumDiscoveryActions.begin(device, network)); + } else if (network.type === 'ripple') { + startAction = await dispatch(RippleDiscoveryActions.begin(device, network)); + } else { + throw new Error(`DiscoveryActions.begin: Unknown network type: ${network.type}`); + } + } catch (error) { dispatch({ type: NOTIFICATION.ADD, payload: { type: 'error', title: 'Discovery error', - message: response.payload.error, + message: error.message, cancelable: true, actions: [ { label: 'Try again', callback: () => { - dispatch(start(device, network)); + dispatch(start(device, networkName)); }, }, ], @@ -174,62 +165,27 @@ const begin = (device: TrezorDevice, network: string): AsyncAction => async (dis const discoveryProcess = getState().discovery.find(d => d.deviceState === device.state && d.network === network); if (dispatch(isProcessInterrupted(discoveryProcess))) return; - const basePath: Array = response.payload.path; - // send data to reducer - dispatch({ - type: DISCOVERY.START, - network, - device, - publicKey: response.payload.publicKey, - chainCode: response.payload.chainCode, - basePath, - }); + dispatch(startAction); // next iteration - dispatch(start(device, network)); + dispatch(start(device, networkName)); }; -const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch): Promise => { - const { completed } = discoveryProcess; +const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { + const { config } = getState().localStorage; + const network = config.networks.find(c => c.shortcut === discoveryProcess.network); + if (!network) return; - 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); - const { network } = discoveryProcess; - - // TODO: check if address was created before + const { completed, accountIndex } = discoveryProcess; + let account: Account; try { - const account = await dispatch(BlockchainActions.discoverAccount(device, ethAddress, network)); - // check for interruption - if (dispatch(isProcessInterrupted(discoveryProcess))) return; - - // 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: ACCOUNT.CREATE, - payload: { - 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, - }, - }); - } - - if (accountIsEmpty) { - dispatch(finish(device, discoveryProcess)); - } else if (!completed) { - dispatch(discoverAccount(device, discoveryProcess)); + if (network.type === 'ethereum') { + account = await dispatch(EthereumDiscoveryActions.discoverAccount(device, discoveryProcess)); + } else if (network.type === 'ripple') { + account = await dispatch(RippleDiscoveryActions.discoverAccount(device, discoveryProcess)); + } else { + throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`); } } catch (error) { dispatch({ @@ -254,6 +210,23 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy ], }, }); + return; + } + + if (dispatch(isProcessInterrupted(discoveryProcess))) return; + + const accountIsEmpty = account.empty; + if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && accountIndex === 0)) { + dispatch({ + type: ACCOUNT.CREATE, + payload: account, + }); + } + + if (accountIsEmpty) { + dispatch(finish(device, discoveryProcess)); + } else if (!completed) { + dispatch(discoverAccount(device, discoveryProcess)); } }; @@ -269,7 +242,7 @@ const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction useEmptyPassphrase: device.useEmptyPassphrase, }); - await dispatch(BlockchainActions.subscribe(discoveryProcess.network)); + // await dispatch(BlockchainActions.subscribe(discoveryProcess.network)); if (dispatch(isProcessInterrupted(discoveryProcess))) return; diff --git a/src/actions/ethereum/EthereumDiscoveryActions.js b/src/actions/ethereum/EthereumDiscoveryActions.js new file mode 100644 index 00000000..ec88fbd7 --- /dev/null +++ b/src/actions/ethereum/EthereumDiscoveryActions.js @@ -0,0 +1,90 @@ +/* @flow */ + +import TrezorConnect from 'trezor-connect'; +import EthereumjsUtil from 'ethereumjs-util'; +import * as DISCOVERY from 'actions/constants/discovery'; + +import type { + PromiseAction, + Dispatch, + TrezorDevice, + Network, + Account, +} from 'flowtype'; +import type { Discovery } from 'reducers/DiscoveryReducer'; +import * as BlockchainActions from '../BlockchainActions'; + +export type DiscoveryStartAction = { + type: typeof DISCOVERY.START, + 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({ + device: { + path: device.path, + instance: device.instance, + state: device.state, + }, + path: network.bip44, + keepSession: true, // acquire and hold session + //useEmptyPassphrase: !device.instance, + useEmptyPassphrase: device.useEmptyPassphrase, + network: network.name, + }); + + // handle TREZOR response error + if (!response.success) { + throw new Error(response.payload.error); + } + + const basePath: Array = response.payload.path; + + return { + type: DISCOVERY.START, + networkType: 'ethereum', + network, + device, + publicKey: response.payload.publicKey, + chainCode: response.payload.chainCode, + basePath, + }; +}; + +export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): PromiseAction => async (dispatch: Dispatch): Promise => { + 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); + const { network } = discoveryProcess; + + // TODO: check if address was created before + const account = await dispatch(BlockchainActions.discoverAccount(device, ethAddress, network)); + + // const accountIsEmpty = account.transactions <= 0 && account.nonce <= 0 && account.balance === '0'; + const empty = account.nonce <= 0 && account.balance === '0'; + + return { + 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, + empty, + }; +}; \ No newline at end of file diff --git a/src/actions/ripple/RippleDiscoveryActions.js b/src/actions/ripple/RippleDiscoveryActions.js index 0af09faa..d1952e6a 100644 --- a/src/actions/ripple/RippleDiscoveryActions.js +++ b/src/actions/ripple/RippleDiscoveryActions.js @@ -2,166 +2,37 @@ import TrezorConnect from 'trezor-connect'; 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, PromiseAction, - PayloadAction, GetState, Dispatch, TrezorDevice, Network, + Account, } from 'flowtype'; -import type { Discovery, State } from 'reducers/DiscoveryReducer'; -import * as BlockchainActions from '../BlockchainActions'; +import type { Discovery } from 'reducers/DiscoveryReducer'; export type DiscoveryStartAction = { type: typeof DISCOVERY.START, - device: TrezorDevice, + networkType: 'ripple', network: Network, - publicKey: string, - chainCode: string, - basePath: Array, -} - -export type DiscoveryWaitingAction = { - type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN, device: TrezorDevice, - network: string, -} - -export type DiscoveryCompleteAction = { - type: typeof DISCOVERY.COMPLETE, - device: TrezorDevice, - network: string, -} - -export type DiscoveryAction = { - type: typeof DISCOVERY.FROM_STORAGE, - payload: State -} | { - type: typeof DISCOVERY.STOP, - device: TrezorDevice -} | DiscoveryStartAction - | DiscoveryWaitingAction - | DiscoveryCompleteAction; - -// There are multiple async methods during discovery process (trezor-connect and blockchain actions) -// This method will check after each of async action if process was interrupted (for example by network change or device disconnect) -const isProcessInterrupted = (process?: Discovery): PayloadAction => (dispatch: Dispatch, getState: GetState): boolean => { - if (!process) { - return false; - } - const { deviceState, network } = process; - const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === deviceState && d.network === network); - if (!discoveryProcess) return false; - return discoveryProcess.interrupted; }; -// Private action -// Called from "this.begin", "this.restore", "this.addAccount" -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; - } +export const begin = (device: TrezorDevice, network: Network): PromiseAction => async (): Promise => ({ + type: DISCOVERY.START, + networkType: 'ripple', + network, + device, +}); - const { discovery } = getState(); - 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.shortcut === network); - if (blockchain && !blockchain.connected && (!discoveryProcess || !discoveryProcess.completed)) { - dispatch({ - type: DISCOVERY.WAITING_FOR_BLOCKCHAIN, - device, - network, - }); - return; - } - - if (!discoveryProcess) { - dispatch(begin(device, network)); - } else if (discoveryProcess.completed && !ignoreCompleted) { - dispatch({ - type: DISCOVERY.COMPLETE, - device, - network, - }); - } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) { - // discovery cycle was interrupted - // start from beginning - dispatch(begin(device, network)); - } else { - dispatch(discoverAccount(device, discoveryProcess)); - } -}; - -// first iteration -// generate public key for this account -// start discovery process -const begin = (device: TrezorDevice, networkName: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { - const { config } = getState().localStorage; - const network = config.networks.find(c => c.shortcut === networkName); - if (!network) return; - - dispatch({ - type: DISCOVERY.WAITING_FOR_DEVICE, - device, - network: networkName, - }); - - // check for interruption - // corner case: DISCOVERY.START wasn't called yet, but Discovery exists in reducer created by DISCOVERY.WAITING_FOR_DEVICE action - // this is why we need to get process instance directly from reducer - const discoveryProcess = getState().discovery.find(d => d.deviceState === device.state && d.network === network); - if (dispatch(isProcessInterrupted(discoveryProcess))) return; - - //const basePath: Array = response.payload.path; - - // send data to reducer - dispatch({ - type: DISCOVERY.START, - network, - device, - publicKey: '', - chainCode: '', - basePath: [0, 0, 0], - }); - - // next iteration - dispatch(start(device, networkName)); -}; - - -const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { +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) return; + if (!network) throw new Error('Discovery network not found'); - const { completed, accountIndex } = discoveryProcess; + const { accountIndex } = discoveryProcess; const path = network.bip44.slice(0).replace('a', accountIndex.toString()); // $FlowIssue npm not released yet @@ -180,127 +51,26 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy useEmptyPassphrase: device.useEmptyPassphrase, }); + // handle TREZOR response error if (!response.success) { - dispatch({ - type: DISCOVERY.STOP, - device, - }); - - dispatch({ - type: NOTIFICATION.ADD, - payload: { - type: 'error', - title: 'Account discovery error', - message: response.payload.error, - cancelable: true, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch(start(device, discoveryProcess.network)); - }, - }, - ], - }, - }); - return; + throw new Error(response.payload.error); } - if (dispatch(isProcessInterrupted(discoveryProcess))) return; - const account = response.payload; - const accountIsEmpty = account.sequence <= 0 && account.balance === '0'; - if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && discoveryProcess.accountIndex === 0)) { - dispatch({ - type: ACCOUNT.CREATE, - payload: { - index: discoveryProcess.accountIndex, - loaded: true, - network: network.shortcut, - deviceID: device.features ? device.features.device_id : '0', - deviceState: device.state || '0', - addressPath: account.path, - address: account.address, - balance: account.balance, - nonce: account.sequence, - block: account.block, - transactions: account.transactions, - }, - }); - } + const empty = account.sequence <= 0 && account.balance === '0'; - if (accountIsEmpty) { - dispatch(finish(device, discoveryProcess)); - } else if (!completed) { - dispatch(discoverAccount(device, discoveryProcess)); - } -}; - -const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch): Promise => { - await TrezorConnect.getFeatures({ - device: { - path: device.path, - instance: device.instance, - state: device.state, - }, - keepSession: false, - // useEmptyPassphrase: !device.instance, - useEmptyPassphrase: device.useEmptyPassphrase, - }); - - // await dispatch(BlockchainActions.subscribe(discoveryProcess.network)); - - if (dispatch(isProcessInterrupted(discoveryProcess))) return; - - dispatch({ - type: DISCOVERY.COMPLETE, - device, - network: discoveryProcess.network, - }); -}; - -export const reconnect = (network: string): PromiseAction => async (dispatch: Dispatch): Promise => { - await dispatch(BlockchainActions.subscribe(network)); - dispatch(restore()); -}; - -// Called after DEVICE.CONNECT ('trezor-connect') or CONNECT.AUTH_DEVICE actions in WalletService -// OR after BlockchainSubscribe in this.reconnect -export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { - // check if current url has "network" parameter - const urlParams = getState().router.location.state; - if (!urlParams.network) return; - - // make sure that "selectedDevice" exists - const selected = getState().wallet.selectedDevice; - if (!selected) return; - - // find discovery process for requested network - const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.network === urlParams.network); - - // if there was no process befor OR process was interrupted/waiting - const shouldStart = !discoveryProcess || (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain); - if (shouldStart) { - dispatch(start(selected, urlParams.network)); - } -}; - -export const stop = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { - const device: ?TrezorDevice = getState().wallet.selectedDevice; - if (!device) return; - - // get all uncompleted discovery processes which assigned to selected device - const discoveryProcesses = getState().discovery.filter(d => d.deviceState === device.state && !d.completed); - if (discoveryProcesses.length > 0) { - dispatch({ - type: DISCOVERY.STOP, - device, - }); - } -}; - -export const addAccount = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => { - const selected = getState().wallet.selectedDevice; - if (!selected) return; - dispatch(start(selected, getState().router.location.state.network, true)); -}; + return { + index: discoveryProcess.accountIndex, + loaded: true, + network: network.shortcut, + deviceID: device.features ? device.features.device_id : '0', + deviceState: device.state || '0', + addressPath: account.path, + address: account.address, + balance: account.balance, + nonce: account.sequence, + block: account.block, + transactions: account.transactions, + empty, + }; +}; \ No newline at end of file diff --git a/src/reducers/AccountsReducer.js b/src/reducers/AccountsReducer.js index 48883d61..480fa76a 100644 --- a/src/reducers/AccountsReducer.js +++ b/src/reducers/AccountsReducer.js @@ -23,6 +23,7 @@ export type Account = { nonce: number; block: number; transactions: number; + empty: boolean; } export type State = Array; From c66383ddbae6e23e1fb0d4a71b1349a57c85335b Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 09:34:31 +0100 Subject: [PATCH 08/72] disable BlockchainActions for non-ethereum networks --- src/actions/BlockchainActions.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 978bf43b..9a43bb6e 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -93,6 +93,7 @@ export const onBlockMined = (coinInfo: any): PromiseAction => async (dispa // incoming "coinInfo" from TrezorConnect is CoinInfo | EthereumNetwork type const network: string = coinInfo.shortcut.toLowerCase(); + if (network !== 'ethereum') return; // try to resolve pending transactions await dispatch(Web3Actions.resolvePendingTransactions(network)); @@ -175,8 +176,11 @@ export const subscribe = (network: string): PromiseAction => async (dispat accounts: [], coin: network, }); - // init web3 instance if not exists - await dispatch(Web3Actions.initWeb3(network)); + + if (network === 'ethereum') { + // init web3 instance if not exists + await dispatch(Web3Actions.initWeb3(network)); + } }; // Conditionally subscribe to blockchain backend From 7956b4a5fb997991a08f409d7e22e1995dfc1378 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 10:16:32 +0100 Subject: [PATCH 09/72] subscribe to blockchain after discovery --- src/actions/DiscoveryActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index 57330eda..dacd66f9 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -242,7 +242,7 @@ const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction useEmptyPassphrase: device.useEmptyPassphrase, }); - // await dispatch(BlockchainActions.subscribe(discoveryProcess.network)); + await dispatch(BlockchainActions.subscribe(discoveryProcess.network)); if (dispatch(isProcessInterrupted(discoveryProcess))) return; From c9eb911756014f773bb4e598151034b147c12595 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 10:17:20 +0100 Subject: [PATCH 10/72] DiscoveryReducer: hdkey only in ehereum network type --- src/reducers/DiscoveryReducer.js | 63 ++++++++++++++------------------ 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/src/reducers/DiscoveryReducer.js b/src/reducers/DiscoveryReducer.js index 093d938b..3bebcf1b 100644 --- a/src/reducers/DiscoveryReducer.js +++ b/src/reducers/DiscoveryReducer.js @@ -34,34 +34,41 @@ export type Discovery = { export type State = Array; const initialState: State = []; +const defaultDiscovery: Discovery = { + network: '', + deviceState: '', + basePath: [], + accountIndex: 0, + interrupted: false, + completed: false, + waitingForDevice: false, + waitingForBlockchain: false, + + publicKey: '', + chainCode: '', + hdKey: null, +}; const findIndex = (state: State, network: string, deviceState: string): number => state.findIndex(d => d.network === network && d.deviceState === deviceState); const start = (state: State, action: DiscoveryStartAction): State => { const deviceState: string = action.device.state || '0'; + const instance: Discovery = { + ...defaultDiscovery, + network: action.network.shortcut, + deviceState, + }; - let hdKey: ?HDKey; - if (action.network.type === 'ethereum') { - hdKey = new HDKey(); + 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; } - const instance: Discovery = { - network: action.network.shortcut, - basePath: action.basePath, - deviceState, - accountIndex: 0, - interrupted: false, - completed: false, - waitingForDevice: false, - waitingForBlockchain: false, - - publicKey: action.publicKey, - chainCode: action.chainCode, - hdKey, - }; - const newState: State = [...state]; const index: number = findIndex(state, action.network.shortcut, deviceState); if (index >= 0) { @@ -112,18 +119,10 @@ const stop = (state: State, device: TrezorDevice): State => { const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State => { const deviceState: string = action.device.state || '0'; const instance: Discovery = { + ...defaultDiscovery, network: action.network, deviceState, - basePath: [], - accountIndex: 0, - interrupted: false, - completed: false, waitingForDevice: true, - waitingForBlockchain: false, - - publicKey: '', - chainCode: '', - hdKey: null, }; const index: number = findIndex(state, action.network, deviceState); @@ -140,18 +139,10 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State = const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): State => { const deviceState: string = action.device.state || '0'; const instance: Discovery = { + ...defaultDiscovery, network: action.network, deviceState, - basePath: [], - accountIndex: 0, - interrupted: false, - completed: false, - waitingForDevice: false, waitingForBlockchain: true, - - publicKey: '', - chainCode: '', - hdKey: null, }; const index: number = findIndex(state, action.network, deviceState); From 120c22775319b5434f451ed5303844d12e9584b2 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 10:25:47 +0100 Subject: [PATCH 11/72] RecevieActions: with ripple call --- src/actions/ReceiveActions.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/actions/ReceiveActions.js b/src/actions/ReceiveActions.js index 95d56119..a039db9e 100644 --- a/src/actions/ReceiveActions.js +++ b/src/actions/ReceiveActions.js @@ -50,7 +50,9 @@ export const showUnverifiedAddress = (): Action => ({ //export const showAddress = (address_n: string): AsyncAction => { export const showAddress = (path: Array): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { const selected = getState().wallet.selectedDevice; - if (!selected) return; + const { network } = getState().selectedAccount; + + if (!selected || !network) return; if (selected && (!selected.connected || !selected.available)) { dispatch({ @@ -60,16 +62,22 @@ export const showAddress = (path: Array): AsyncAction => async (dispatch return; } - const response = await TrezorConnect.ethereumGetAddress({ + const params = { device: { path: selected.path, instance: selected.instance, state: selected.state, }, path, - // useEmptyPassphrase: !selected.instance, useEmptyPassphrase: selected.useEmptyPassphrase, - }); + }; + + let response; + if (network.type === 'ethereum') { + response = await TrezorConnect.ethereumGetAddress(params); + } else { + response = await TrezorConnect.rippleGetAddress(params); + } if (response && response.success) { dispatch({ From ee8f884cedae10f9799f13a539e9f3d63ec8e43b Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 15:19:20 +0100 Subject: [PATCH 12/72] appConfig.json with all networks --- public/data/appConfig.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/data/appConfig.json b/public/data/appConfig.json index fc345eef..b484b97d 100644 --- a/public/data/appConfig.json +++ b/public/data/appConfig.json @@ -10,9 +10,7 @@ "tx": "https://etherscan.io/tx/", "address": "https://etherscan.io/address/" } - } - ], - "networks2": [ + }, { "type": "ethereum", "name": "Ethereum", From 3774110a83137fbb59e2165858c4ed8cb3f2092f Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 15:20:30 +0100 Subject: [PATCH 13/72] handle NOT_SUPPORTED methods in Discovery --- src/actions/DiscoveryActions.js | 18 ++++++++++++++++-- src/actions/constants/discovery.js | 1 + src/reducers/DiscoveryReducer.js | 24 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index dacd66f9..773caeb1 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -1,6 +1,6 @@ /* @flow */ -import TrezorConnect from 'trezor-connect'; +import TrezorConnect, { UI } from 'trezor-connect'; import * as DISCOVERY from 'actions/constants/discovery'; import * as ACCOUNT from 'actions/constants/account'; import * as NOTIFICATION from 'actions/constants/notification'; @@ -23,7 +23,7 @@ import * as RippleDiscoveryActions from './ripple/RippleDiscoveryActions'; export type DiscoveryStartAction = EthereumDiscoveryActions.DiscoveryStartAction | RippleDiscoveryActions.DiscoveryStartAction; export type DiscoveryWaitingAction = { - type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN, + type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN | typeof DISCOVERY.NOT_SUPPORTED, device: TrezorDevice, network: string, } @@ -78,6 +78,10 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean) const { discovery } = getState(); const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network); + if (discoveryProcess && discoveryProcess.notSupported) { + return; + } + if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { dispatch({ type: DISCOVERY.WAITING_FOR_DEVICE, @@ -188,6 +192,16 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`); } } catch (error) { + // handle unsupported firmware error + if (error.message === UI.FIRMWARE_NOT_SUPPORTED) { + dispatch({ + type: DISCOVERY.NOT_SUPPORTED, + device, + network: discoveryProcess.network, + }); + return; + } + dispatch({ type: DISCOVERY.STOP, device, diff --git a/src/actions/constants/discovery.js b/src/actions/constants/discovery.js index e7287b1e..3b6a49a0 100644 --- a/src/actions/constants/discovery.js +++ b/src/actions/constants/discovery.js @@ -3,6 +3,7 @@ export const START: 'discovery__start' = 'discovery__start'; export const STOP: 'discovery__stop' = 'discovery__stop'; +export const NOT_SUPPORTED: 'discovery__not_supported' = 'discovery__not_supported'; export const COMPLETE: 'discovery__complete' = 'discovery__complete'; export const WAITING_FOR_DEVICE: 'discovery__waiting_for_device' = 'discovery__waiting_for_device'; export const WAITING_FOR_BLOCKCHAIN: 'discovery__waiting_for_blockchain' = 'discovery__waiting_for_blockchain'; diff --git a/src/reducers/DiscoveryReducer.js b/src/reducers/DiscoveryReducer.js index 3bebcf1b..e42bd9c5 100644 --- a/src/reducers/DiscoveryReducer.js +++ b/src/reducers/DiscoveryReducer.js @@ -26,6 +26,7 @@ export type Discovery = { completed: boolean; waitingForDevice: boolean; waitingForBlockchain: boolean; + notSupported: boolean; publicKey: string; // used in ethereum only chainCode: string; // used in ethereum only @@ -43,6 +44,7 @@ const defaultDiscovery: Discovery = { completed: false, waitingForDevice: false, waitingForBlockchain: false, + notSupported: false, publicKey: '', chainCode: '', @@ -156,6 +158,26 @@ const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): Sta return newState; }; +const notSupported = (state: State, action: DiscoveryWaitingAction): State => { + const deviceState: string = action.device.state || '0'; + const instance: Discovery = { + ...defaultDiscovery, + network: action.network, + deviceState, + notSupported: true, + }; + + const index: number = findIndex(state, action.network, deviceState); + const newState: State = [...state]; + if (index >= 0) { + newState[index] = instance; + } else { + newState.push(instance); + } + + return newState; +}; + export default function discovery(state: State = initialState, action: Action): State { switch (action.type) { case DISCOVERY.START: @@ -170,6 +192,8 @@ export default function discovery(state: State = initialState, action: Action): return waitingForDevice(state, action); case DISCOVERY.WAITING_FOR_BLOCKCHAIN: return waitingForBlockchain(state, action); + case DISCOVERY.NOT_SUPPORTED: + return notSupported(state, action); case DISCOVERY.FROM_STORAGE: return action.payload.map((d) => { const hdKey: HDKey = new HDKey(); From 1750bb41dcba2a7262836badec22b37ec7fb2490 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 15:45:44 +0100 Subject: [PATCH 14/72] split Summary components to coin specific files --- src/views/Wallet/Container.js | 0 .../Summary/components/Balance/index.js | 5 +- .../EthereumSummary}/Container.js | 0 .../containers/EthereumSummary/index.js | 160 +++++++++++++++ .../Wallet/views/Account/Summary/index.js | 187 +++--------------- src/views/index.js | 2 +- 6 files changed, 191 insertions(+), 163 deletions(-) delete mode 100644 src/views/Wallet/Container.js rename src/views/Wallet/views/Account/Summary/{ => containers/EthereumSummary}/Container.js (100%) create mode 100644 src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js diff --git a/src/views/Wallet/Container.js b/src/views/Wallet/Container.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/views/Wallet/views/Account/Summary/components/Balance/index.js b/src/views/Wallet/views/Account/Summary/components/Balance/index.js index c7c978fc..9ac46d64 100644 --- a/src/views/Wallet/views/Account/Summary/components/Balance/index.js +++ b/src/views/Wallet/views/Account/Summary/components/Balance/index.js @@ -7,13 +7,12 @@ import colors from 'config/colors'; import ICONS from 'config/icons'; import { FONT_SIZE, FONT_WEIGHT } from 'config/variables'; -import type { Network } from 'flowtype'; -import type { Props as BaseProps } from '../../Container'; +import type { Network, State as ReducersState } from 'flowtype'; type Props = { network: Network, balance: string, - fiat: $ElementType, + fiat: $ElementType, } type State = { diff --git a/src/views/Wallet/views/Account/Summary/Container.js b/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/Container.js similarity index 100% rename from src/views/Wallet/views/Account/Summary/Container.js rename to src/views/Wallet/views/Account/Summary/containers/EthereumSummary/Container.js diff --git a/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js b/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js new file mode 100644 index 00000000..fc88851b --- /dev/null +++ b/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js @@ -0,0 +1,160 @@ +/* @flow */ +import styled from 'styled-components'; +import React from 'react'; +import { H2 } from 'components/Heading'; +import BigNumber from 'bignumber.js'; +import Icon from 'components/Icon'; +import { AsyncSelect } from 'components/Select'; +import ICONS from 'config/icons'; +import colors from 'config/colors'; +import Tooltip from 'components/Tooltip'; +import Content from 'views/Wallet/components/Content'; + +import CoinLogo from 'components/images/CoinLogo'; +import * as stateUtils from 'reducers/utils'; +import Link from 'components/Link'; +import { FONT_WEIGHT, FONT_SIZE } from 'config/variables'; +import AccountBalance from '../../components/Balance'; +import AddedToken from '../../components/Token'; + +import type { Props } from './Container'; + +const AccountHeading = styled.div` + padding: 0 0 30px 0; + display: flex; + justify-content: space-between; + align-items: center; +`; + +const H2Wrapper = styled.div` + display: flex; + align-items: center; + padding: 20px 0; +`; + +const StyledTooltip = styled(Tooltip)` + position: relative; + top: 2px; +`; + +const AccountName = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +const AccountTitle = styled.div` + font-size: ${FONT_SIZE.WALLET_TITLE}; + font-weight: ${FONT_WEIGHT.BASE}; + color: ${colors.WALLET_TITLE}; +`; + +const StyledCoinLogo = styled(CoinLogo)` + margin-right: 10px; +`; + +const StyledIcon = styled(Icon)` + position: relative; + top: -7px; + &:hover { + cursor: pointer; + } +`; + +const AsyncSelectWrapper = styled.div` + padding-bottom: 32px; +`; + +const AddedTokensWrapper = styled.div``; + +const AccountSummary = (props: Props) => { + const device = props.wallet.selectedDevice; + const { + account, + network, + tokens, + pending, + shouldRender, + } = props.selectedAccount; + + // flow + if (!device || !account || !network || !shouldRender) return null; + + const explorerLink: string = `${network.explorer.address}${account.address}`; + const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol); + const balance: string = new BigNumber(account.balance).minus(pendingAmount).toString(10); + + return ( + + + + + + Account #{parseInt(account.index, 10) + 1} + + See full transaction history + + + +

Tokens

+ + + +
+ + 'Loading...'} + noOptionsMessage={() => 'Token not found'} + onChange={(token) => { + if (token.name) { + const isAdded = tokens.find(t => t.symbol === token.symbol); + if (!isAdded) { + props.addToken(token, account); + } + } + }} + loadOptions={input => props.loadTokens(input, account.network)} + formatOptionLabel={(option) => { + const isAdded = tokens.find(t => t.symbol === option.symbol); + if (isAdded) { + return `${option.name} (Already added)`; + } + return option.name; + }} + getOptionLabel={option => option.name} + getOptionValue={option => option.symbol} + /> + + + {tokens.map(token => ( + + ))} + +
+
+ ); +}; + +export default AccountSummary; diff --git a/src/views/Wallet/views/Account/Summary/index.js b/src/views/Wallet/views/Account/Summary/index.js index 197b285c..4fc7082b 100644 --- a/src/views/Wallet/views/Account/Summary/index.js +++ b/src/views/Wallet/views/Account/Summary/index.js @@ -1,165 +1,34 @@ /* @flow */ -import styled from 'styled-components'; import React from 'react'; -import { H2 } from 'components/Heading'; -import BigNumber from 'bignumber.js'; -import Icon from 'components/Icon'; -import { AsyncSelect } from 'components/Select'; -import ICONS from 'config/icons'; -import colors from 'config/colors'; -import Tooltip from 'components/Tooltip'; -import Content from 'views/Wallet/components/Content'; +import { connect } from 'react-redux'; -import CoinLogo from 'components/images/CoinLogo'; -import * as stateUtils from 'reducers/utils'; -import Link from 'components/Link'; -import { FONT_WEIGHT, FONT_SIZE } from 'config/variables'; -import AccountBalance from './components/Balance'; -import AddedToken from './components/Token'; -import AddTokenMessage from './components/AddTokenMessage'; +import type { State } from 'flowtype'; +import EthereumSummary from './containers/EthereumSummary/Container'; -import type { Props } from './Container'; - -const AccountHeading = styled.div` - padding: 0 0 30px 0; - display: flex; - justify-content: space-between; - align-items: center; -`; - -const H2Wrapper = styled.div` - display: flex; - align-items: center; - padding: 20px 0; -`; - -const StyledTooltip = styled(Tooltip)` - position: relative; - top: 2px; -`; - -const AccountName = styled.div` - display: flex; - justify-content: center; - align-items: center; -`; - -const AccountTitle = styled.div` - font-size: ${FONT_SIZE.WALLET_TITLE}; - font-weight: ${FONT_WEIGHT.BASE}; - color: ${colors.WALLET_TITLE}; -`; - -const StyledCoinLogo = styled(CoinLogo)` - margin-right: 10px; -`; - -const StyledIcon = styled(Icon)` - position: relative; - top: -7px; - - &:hover { - cursor: pointer; - } -`; - -const AsyncSelectWrapper = styled.div` - padding-bottom: 32px; -`; - -const AddedTokensWrapper = styled.div``; - -const AccountSummary = (props: Props) => { - const device = props.wallet.selectedDevice; - const { - account, - network, - tokens, - pending, - loader, - shouldRender, - } = props.selectedAccount; - - const { type, title, message } = loader; - - if (!device || !account || !network || !shouldRender) return ; - - const explorerLink: string = `${network.explorer.address}${account.address}`; - const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol); - const balance: string = new BigNumber(account.balance).minus(pendingAmount).toString(10); - - return ( - - - - - - Account #{parseInt(account.index, 10) + 1} - - See full transaction history - - - -

Tokens

- - - -
- - 'Loading...'} - noOptionsMessage={() => 'Token not found'} - onChange={(token) => { - if (token.name) { - const isAdded = tokens.find(t => t.symbol === token.symbol); - if (!isAdded) { - props.addToken(token, account); - } - } - }} - loadOptions={input => props.loadTokens(input, account.network)} - formatOptionLabel={(option) => { - const isAdded = tokens.find(t => t.symbol === option.symbol); - if (isAdded) { - return `${option.name} (Already added)`; - } - return option.name; - }} - getOptionLabel={option => option.name} - getOptionValue={option => option.symbol} - /> - - - { tokens.length < 1 && () } - {tokens.map(token => ( - - ))} - -
-
- ); +type StateProps = { + selectedAccount: $ElementType, + summary: $ElementType, + wallet: $ElementType, + tokens: $ElementType, + fiat: $ElementType, + localStorage: $ElementType, }; -export default AccountSummary; +export type Props = StateProps; + +type WrapperProps = { + selectedAccount: $ElementType, +} + +// wrapper will return container for requested network type +export default connect((state: State): WrapperProps => ({ + selectedAccount: state.selectedAccount, +}), null)((props) => { + const { network } = props.selectedAccount; + if (!network) return null; + + if (network.type === 'ripple') { + return ; + } + return ; +}); diff --git a/src/views/index.js b/src/views/index.js index 20542adf..e772cfbe 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -16,7 +16,7 @@ import ImportView from 'views/Landing/views/Import/Container'; // wallet views import WalletContainer from 'views/Wallet'; -import AccountSummary from 'views/Wallet/views/Account/Summary/Container'; +import AccountSummary from 'views/Wallet/views/Account/Summary'; import AccountSend from 'views/Wallet/views/Account/Send/Container'; import AccountReceive from 'views/Wallet/views/Account/Receive/Container'; import AccountSignVerify from 'views/Wallet/views/Account/SignVerify/Container'; From 45583ac4773edbbdfdda6686b2b6d21e83645d3e Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 15:49:08 +0100 Subject: [PATCH 15/72] send account to blockchain subscribe method --- src/actions/BlockchainActions.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 9a43bb6e..efddb58b 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -170,10 +170,9 @@ export const onNotification = (payload: any): PromiseAction => async (disp 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); // eslint-disable-line no-unused-vars + const accounts: Array = getState().accounts.filter(a => a.network === network).map(a => a.address); // eslint-disable-line no-unused-vars await TrezorConnect.blockchainSubscribe({ - // accounts: addresses, - accounts: [], + accounts, coin: network, }); From 9d344920df110065c30bc080e77ad6af9f98b066 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 15:57:37 +0100 Subject: [PATCH 16/72] EthereumAccountSummary: restore after rebase --- .../Summary/containers/EthereumSummary/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js b/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js index fc88851b..0ad14e68 100644 --- a/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js +++ b/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js @@ -16,6 +16,7 @@ import Link from 'components/Link'; import { FONT_WEIGHT, FONT_SIZE } from 'config/variables'; import AccountBalance from '../../components/Balance'; import AddedToken from '../../components/Token'; +import AddTokenMessage from '../../components/AddTokenMessage'; import type { Props } from './Container'; @@ -74,11 +75,13 @@ const AccountSummary = (props: Props) => { network, tokens, pending, + loader, shouldRender, } = props.selectedAccount; - // flow - if (!device || !account || !network || !shouldRender) return null; + const { type, title, message } = loader; + + if (!device || !account || !network || !shouldRender) return ; const explorerLink: string = `${network.explorer.address}${account.address}`; const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol); @@ -143,6 +146,7 @@ const AccountSummary = (props: Props) => { /> + { tokens.length < 1 && () } {tokens.map(token => ( { ); }; -export default AccountSummary; +export default AccountSummary; \ No newline at end of file From 7f56cdbcde597343a3040d3487ceeee20ab8a0da Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 19:36:11 +0100 Subject: [PATCH 17/72] handle unsupported and/or outdated firmware in discovery process --- src/actions/DiscoveryActions.js | 20 +++++++---- src/actions/SelectedAccountActions.js | 18 ++++++++++ src/actions/constants/discovery.js | 3 +- src/reducers/DiscoveryReducer.js | 33 +++++++++---------- .../components/AccountMenu/index.js | 4 +++ 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index 773caeb1..60c99914 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -23,7 +23,7 @@ import * as RippleDiscoveryActions from './ripple/RippleDiscoveryActions'; export type DiscoveryStartAction = EthereumDiscoveryActions.DiscoveryStartAction | RippleDiscoveryActions.DiscoveryStartAction; export type DiscoveryWaitingAction = { - type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN | typeof DISCOVERY.NOT_SUPPORTED, + type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN | typeof DISCOVERY.FIRMWARE_NOT_SUPPORTED | typeof DISCOVERY.FIRMWARE_OUTDATED, device: TrezorDevice, network: string, } @@ -78,10 +78,6 @@ const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean) const { discovery } = getState(); const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network); - if (discoveryProcess && discoveryProcess.notSupported) { - return; - } - if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) { dispatch({ type: DISCOVERY.WAITING_FOR_DEVICE, @@ -192,10 +188,20 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`); } } catch (error) { - // handle unsupported firmware error + // handle not supported firmware error if (error.message === UI.FIRMWARE_NOT_SUPPORTED) { dispatch({ - type: DISCOVERY.NOT_SUPPORTED, + type: DISCOVERY.FIRMWARE_NOT_SUPPORTED, + device, + network: discoveryProcess.network, + }); + return; + } + + // handle outdated firmware error + if (error.message === UI.FIRMWARE) { + dispatch({ + type: DISCOVERY.FIRMWARE_OUTDATED, device, network: discoveryProcess.network, }); diff --git a/src/actions/SelectedAccountActions.js b/src/actions/SelectedAccountActions.js index af73d0cd..7b61eda0 100644 --- a/src/actions/SelectedAccountActions.js +++ b/src/actions/SelectedAccountActions.js @@ -67,6 +67,24 @@ const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): if (account) return null; // account not found (yet). checking why... + if (discovery && discovery.fwOutdated) { + return { + type: 'info', + title: `Device ${device.instanceLabel} firmware is outdated`, + message: 'TODO: update firmware explanation', + shouldRender: false, + }; + } + + if (discovery && discovery.fwNotSupported) { + return { + type: 'info', + title: `Device ${device.instanceLabel} is not supported`, + message: 'TODO: model T1 not supported explanation', + shouldRender: false, + }; + } + if (!discovery || (discovery.waitingForDevice || discovery.interrupted)) { if (device.connected) { // case 1: device is connected but discovery not started yet (probably waiting for auth) diff --git a/src/actions/constants/discovery.js b/src/actions/constants/discovery.js index 3b6a49a0..a1b2a65e 100644 --- a/src/actions/constants/discovery.js +++ b/src/actions/constants/discovery.js @@ -3,7 +3,8 @@ export const START: 'discovery__start' = 'discovery__start'; export const STOP: 'discovery__stop' = 'discovery__stop'; -export const NOT_SUPPORTED: 'discovery__not_supported' = 'discovery__not_supported'; +export const FIRMWARE_NOT_SUPPORTED: 'discovery__fw_not_supported' = 'discovery__fw_not_supported'; +export const FIRMWARE_OUTDATED: 'discovery__fw_outdated' = 'discovery__fw_outdated'; export const COMPLETE: 'discovery__complete' = 'discovery__complete'; export const WAITING_FOR_DEVICE: 'discovery__waiting_for_device' = 'discovery__waiting_for_device'; export const WAITING_FOR_BLOCKCHAIN: 'discovery__waiting_for_blockchain' = 'discovery__waiting_for_blockchain'; diff --git a/src/reducers/DiscoveryReducer.js b/src/reducers/DiscoveryReducer.js index e42bd9c5..384a1175 100644 --- a/src/reducers/DiscoveryReducer.js +++ b/src/reducers/DiscoveryReducer.js @@ -26,7 +26,8 @@ export type Discovery = { completed: boolean; waitingForDevice: boolean; waitingForBlockchain: boolean; - notSupported: boolean; + fwNotSupported: boolean; + fwOutdated: boolean; publicKey: string; // used in ethereum only chainCode: string; // used in ethereum only @@ -44,7 +45,8 @@ const defaultDiscovery: Discovery = { completed: false, waitingForDevice: false, waitingForBlockchain: false, - notSupported: false, + fwNotSupported: false, + fwOutdated: false, publicKey: '', chainCode: '', @@ -159,23 +161,16 @@ const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): Sta }; const notSupported = (state: State, action: DiscoveryWaitingAction): State => { - const deviceState: string = action.device.state || '0'; - const instance: Discovery = { - ...defaultDiscovery, - network: action.network, - deviceState, - notSupported: true, - }; + const affectedProcesses = state.filter(d => d.deviceState === action.device.state && d.network === action.network); + const otherProcesses = state.filter(d => affectedProcesses.indexOf(d) === -1); - const index: number = findIndex(state, action.network, deviceState); - const newState: State = [...state]; - if (index >= 0) { - newState[index] = instance; - } else { - newState.push(instance); - } + const changedProcesses = affectedProcesses.map(d => ({ + ...d, + fwOutdated: action.type === DISCOVERY.FIRMWARE_OUTDATED, + fwNotSupported: action.type === DISCOVERY.FIRMWARE_NOT_SUPPORTED, + })); - return newState; + return otherProcesses.concat(changedProcesses); }; export default function discovery(state: State = initialState, action: Action): State { @@ -192,7 +187,9 @@ export default function discovery(state: State = initialState, action: Action): return waitingForDevice(state, action); case DISCOVERY.WAITING_FOR_BLOCKCHAIN: return waitingForBlockchain(state, action); - case DISCOVERY.NOT_SUPPORTED: + case DISCOVERY.FIRMWARE_NOT_SUPPORTED: + return notSupported(state, action); + case DISCOVERY.FIRMWARE_OUTDATED: return notSupported(state, action); case DISCOVERY.FROM_STORAGE: return action.payload.map((d) => { diff --git a/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js b/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js index f41efa2a..24da4cdb 100644 --- a/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/AccountMenu/index.js @@ -229,6 +229,10 @@ const AccountMenu = (props: Props) => { ); } + if (discovery && (discovery.fwNotSupported || discovery.fwOutdated)) { + discoveryStatus = null; + } + return ( From 4acb5974015252bc761e7064da369ca9ba385fe0 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 26 Nov 2018 19:46:45 +0100 Subject: [PATCH 18/72] split Summary view into networks --- .../EthereumSummary => ethereum}/Container.js | 0 .../EthereumSummary => ethereum}/index.js | 6 +- .../Wallet/views/Account/Summary/index.js | 26 ++-- .../views/Account/Summary/ripple/Container.js | 45 +++++++ .../views/Account/Summary/ripple/index.js | 114 ++++++++++++++++++ 5 files changed, 172 insertions(+), 19 deletions(-) rename src/views/Wallet/views/Account/Summary/{containers/EthereumSummary => ethereum}/Container.js (100%) rename src/views/Wallet/views/Account/Summary/{containers/EthereumSummary => ethereum}/index.js (96%) create mode 100644 src/views/Wallet/views/Account/Summary/ripple/Container.js create mode 100644 src/views/Wallet/views/Account/Summary/ripple/index.js diff --git a/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/Container.js b/src/views/Wallet/views/Account/Summary/ethereum/Container.js similarity index 100% rename from src/views/Wallet/views/Account/Summary/containers/EthereumSummary/Container.js rename to src/views/Wallet/views/Account/Summary/ethereum/Container.js diff --git a/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js b/src/views/Wallet/views/Account/Summary/ethereum/index.js similarity index 96% rename from src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js rename to src/views/Wallet/views/Account/Summary/ethereum/index.js index 0ad14e68..2b55ca61 100644 --- a/src/views/Wallet/views/Account/Summary/containers/EthereumSummary/index.js +++ b/src/views/Wallet/views/Account/Summary/ethereum/index.js @@ -14,9 +14,9 @@ import CoinLogo from 'components/images/CoinLogo'; import * as stateUtils from 'reducers/utils'; import Link from 'components/Link'; import { FONT_WEIGHT, FONT_SIZE } from 'config/variables'; -import AccountBalance from '../../components/Balance'; -import AddedToken from '../../components/Token'; -import AddTokenMessage from '../../components/AddTokenMessage'; +import AccountBalance from '../components/Balance'; +import AddedToken from '../components/Token'; +import AddTokenMessage from '../components/AddTokenMessage'; import type { Props } from './Container'; diff --git a/src/views/Wallet/views/Account/Summary/index.js b/src/views/Wallet/views/Account/Summary/index.js index 4fc7082b..a0a6a5f8 100644 --- a/src/views/Wallet/views/Account/Summary/index.js +++ b/src/views/Wallet/views/Account/Summary/index.js @@ -3,32 +3,26 @@ import React from 'react'; import { connect } from 'react-redux'; import type { State } from 'flowtype'; -import EthereumSummary from './containers/EthereumSummary/Container'; - -type StateProps = { - selectedAccount: $ElementType, - summary: $ElementType, - wallet: $ElementType, - tokens: $ElementType, - fiat: $ElementType, - localStorage: $ElementType, -}; - -export type Props = StateProps; +import EthereumSummary from './ethereum/Container'; +import RippleSummary from './ripple/Container'; type WrapperProps = { selectedAccount: $ElementType, } -// wrapper will return container for requested network type +// return container for requested network type export default connect((state: State): WrapperProps => ({ selectedAccount: state.selectedAccount, }), null)((props) => { const { network } = props.selectedAccount; if (!network) return null; - if (network.type === 'ripple') { - return ; + switch (network.type) { + case 'ethereum': + return ; + case 'ripple': + return ; + default: + return null; } - return ; }); diff --git a/src/views/Wallet/views/Account/Summary/ripple/Container.js b/src/views/Wallet/views/Account/Summary/ripple/Container.js new file mode 100644 index 00000000..18d18175 --- /dev/null +++ b/src/views/Wallet/views/Account/Summary/ripple/Container.js @@ -0,0 +1,45 @@ +/* @flow */ +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; + +import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; +import * as TokenActions from 'actions/TokenActions'; + +import type { State, Dispatch } from 'flowtype'; +import Summary from './index'; + +type OwnProps = { } + +type StateProps = { + selectedAccount: $ElementType, + summary: $ElementType, + wallet: $ElementType, + tokens: $ElementType, + fiat: $ElementType, + localStorage: $ElementType, +}; + +type DispatchProps = { + addToken: typeof TokenActions.add, + loadTokens: typeof TokenActions.load, + removeToken: typeof TokenActions.remove, +} + +export type Props = StateProps & DispatchProps; + +const mapStateToProps: MapStateToProps = (state: State): StateProps => ({ + selectedAccount: state.selectedAccount, + summary: state.summary, + wallet: state.wallet, + tokens: state.tokens, + fiat: state.fiat, + localStorage: state.localStorage, +}); + +const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + addToken: bindActionCreators(TokenActions.add, dispatch), + loadTokens: bindActionCreators(TokenActions.load, dispatch), + removeToken: bindActionCreators(TokenActions.remove, dispatch), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Summary); \ No newline at end of file diff --git a/src/views/Wallet/views/Account/Summary/ripple/index.js b/src/views/Wallet/views/Account/Summary/ripple/index.js new file mode 100644 index 00000000..5f7cf8d2 --- /dev/null +++ b/src/views/Wallet/views/Account/Summary/ripple/index.js @@ -0,0 +1,114 @@ +/* @flow */ +import styled from 'styled-components'; +import React from 'react'; +import { H2 } from 'components/Heading'; +import BigNumber from 'bignumber.js'; +import Icon from 'components/Icon'; +import ICONS from 'config/icons'; +import colors from 'config/colors'; +import Tooltip from 'components/Tooltip'; +import Content from 'views/Wallet/components/Content'; + +import CoinLogo from 'components/images/CoinLogo'; +import * as stateUtils from 'reducers/utils'; +import Link from 'components/Link'; +import { FONT_WEIGHT, FONT_SIZE } from 'config/variables'; +import AccountBalance from '../components/Balance'; + +import type { Props } from './Container'; + +const AccountHeading = styled.div` + padding: 0 0 30px 0; + display: flex; + justify-content: space-between; + align-items: center; +`; + +const H2Wrapper = styled.div` + display: flex; + align-items: center; + padding: 20px 0; +`; + +const StyledTooltip = styled(Tooltip)` + position: relative; + top: 2px; +`; + +const AccountName = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +const AccountTitle = styled.div` + font-size: ${FONT_SIZE.WALLET_TITLE}; + font-weight: ${FONT_WEIGHT.BASE}; + color: ${colors.WALLET_TITLE}; +`; + +const StyledCoinLogo = styled(CoinLogo)` + margin-right: 10px; +`; + +const StyledIcon = styled(Icon)` + position: relative; + top: -7px; + &:hover { + cursor: pointer; + } +`; + +const AccountSummary = (props: Props) => { + const device = props.wallet.selectedDevice; + const { + account, + network, + pending, + loader, + shouldRender, + } = props.selectedAccount; + + const { type, title, message } = loader; + + if (!device || !account || !network || !shouldRender) return ; + + const explorerLink: string = `${network.explorer.address}${account.address}`; + const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol); + const balance: string = new BigNumber(account.balance).minus(pendingAmount).toString(10); + + return ( + + + + + + Account #{parseInt(account.index, 10) + 1} + + See full transaction history + + + +

History

+ + + +
+
+
+ ); +}; + +export default AccountSummary; \ No newline at end of file From c9d30da6b2fb21eaa041db079a541c8909a865bb Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 27 Nov 2018 10:56:10 +0100 Subject: [PATCH 19/72] split SendForm into networks --- .../Send/components/AdvancedForm/index.js | 2 +- .../components/PendingTransactions/index.js | 2 +- .../Account/Send/{ => ethereum}/Container.js | 0 .../views/Account/Send/ethereum/index.js | 406 ++++++++++++++++++ src/views/Wallet/views/Account/Send/index.js | 30 +- .../views/Account/Send/ripple/Container.js | 39 ++ .../Wallet/views/Account/Send/ripple/index.js | 406 ++++++++++++++++++ 7 files changed, 882 insertions(+), 3 deletions(-) rename src/views/Wallet/views/Account/Send/{ => ethereum}/Container.js (100%) create mode 100644 src/views/Wallet/views/Account/Send/ethereum/index.js create mode 100644 src/views/Wallet/views/Account/Send/ripple/Container.js create mode 100644 src/views/Wallet/views/Account/Send/ripple/index.js diff --git a/src/views/Wallet/views/Account/Send/components/AdvancedForm/index.js b/src/views/Wallet/views/Account/Send/components/AdvancedForm/index.js index 6af73ddd..bd0fb790 100644 --- a/src/views/Wallet/views/Account/Send/components/AdvancedForm/index.js +++ b/src/views/Wallet/views/Account/Send/components/AdvancedForm/index.js @@ -11,7 +11,7 @@ import Icon from 'components/Icon'; import ICONS from 'config/icons'; import { FONT_SIZE } from 'config/variables'; -import type { Props as BaseProps } from '../../Container'; +import type { Props as BaseProps } from '../../ethereum/Container'; type Props = BaseProps & { children: React.Node, diff --git a/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js b/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js index 63c5dfb8..92b55827 100644 --- a/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js +++ b/src/views/Wallet/views/Account/Send/components/PendingTransactions/index.js @@ -9,7 +9,7 @@ import ScaleText from 'react-scale-text'; import type { Network } from 'reducers/LocalStorageReducer'; import type { Token } from 'reducers/TokensReducer'; -import type { Props as BaseProps } from '../../Container'; +import type { BaseProps } from '../../index'; type Props = { pending: $PropertyType<$ElementType, 'pending'>, diff --git a/src/views/Wallet/views/Account/Send/Container.js b/src/views/Wallet/views/Account/Send/ethereum/Container.js similarity index 100% rename from src/views/Wallet/views/Account/Send/Container.js rename to src/views/Wallet/views/Account/Send/ethereum/Container.js diff --git a/src/views/Wallet/views/Account/Send/ethereum/index.js b/src/views/Wallet/views/Account/Send/ethereum/index.js new file mode 100644 index 00000000..97638549 --- /dev/null +++ b/src/views/Wallet/views/Account/Send/ethereum/index.js @@ -0,0 +1,406 @@ +/* @flow */ + +import React from 'react'; +import styled, { css } from 'styled-components'; +import { Select } from 'components/Select'; +import Button from 'components/Button'; +import Input from 'components/inputs/Input'; +import Icon from 'components/Icon'; +import Link from 'components/Link'; +import ICONS from 'config/icons'; +import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables'; +import colors from 'config/colors'; +import P from 'components/Paragraph'; +import { H2 } from 'components/Heading'; +import Content from 'views/Wallet/components/Content'; +import type { Token } from 'flowtype'; +import AdvancedForm from '../components/AdvancedForm'; +import PendingTransactions from '../components/PendingTransactions'; + +import type { Props } from './Container'; + +// TODO: Decide on a small screen width for the whole app +// and put it inside config/variables.js +const SmallScreenWidth = '850px'; + +const AmountInputLabelWrapper = styled.div` + display: flex; + justify-content: space-between; +`; + +const AmountInputLabel = styled.span` + text-align: right; + color: ${colors.TEXT_SECONDARY}; +`; + +const InputRow = styled.div` + margin: 20px 0; +`; + +const SetMaxAmountButton = styled(Button)` + height: 40px; + padding: 0 10px; + display: flex; + align-items: center; + justify-content: center; + + font-size: ${FONT_SIZE.SMALLER}; + font-weight: ${FONT_WEIGHT.SMALLEST}; + color: ${colors.TEXT_SECONDARY}; + + border-radius: 0; + border: 1px solid ${colors.DIVIDER}; + border-right: 0; + border-left: 0; + background: transparent; + transition: ${TRANSITION.HOVER}; + + &:hover { + background: ${colors.GRAY_LIGHT}; + } + + ${props => props.isActive && css` + color: ${colors.WHITE}; + background: ${colors.GREEN_PRIMARY}; + border-color: ${colors.GREEN_PRIMARY}; + + &:hover { + background: ${colors.GREEN_SECONDARY}; + } + + &:active { + background: ${colors.GREEN_TERTIARY}; + } + `} +`; + +const CurrencySelect = styled(Select)` + min-width: 77px; + height: 40px; + flex: 0.2; +`; + +const FeeOptionWrapper = styled.div` + display: flex; + justify-content: space-between; +`; + +const FeeLabelWrapper = styled.div` + display: flex; + align-items: center; + margin-bottom: 4px; +`; + +const FeeLabel = styled.span` + color: ${colors.TEXT_SECONDARY}; +`; + +const UpdateFeeWrapper = styled.span` + margin-left: 8px; + display: flex; + align-items: center; + font-size: ${FONT_SIZE.SMALLER}; + color: ${colors.WARNING_PRIMARY}; +`; + +const StyledLink = styled(Link)` + margin-left: 4px; + white-space: nowrap; +`; + +const ToggleAdvancedSettingsWrapper = styled.div` + min-height: 40px; + margin-bottom: 20px; + display: flex; + flex-direction: row; + justify-content: space-between; + + @media screen and (max-width: ${SmallScreenWidth}) { + ${props => (props.isAdvancedSettingsHidden && css` + flex-direction: column; + `)} + } +`; + +const ToggleAdvancedSettingsButton = styled(Button)` + min-height: 40px; + padding: 0; + display: flex; + align-items: center; + font-weight: ${FONT_WEIGHT.BIGGER}; +`; + +const SendButton = styled(Button)` + min-width: ${props => (props.isAdvancedSettingsHidden ? '50%' : '100%')}; + font-size: 13px; + + @media screen and (max-width: ${SmallScreenWidth}) { + margin-top: ${props => (props.isAdvancedSettingsHidden ? '10px' : 0)}; + } +`; + +const AdvancedSettingsIcon = styled(Icon)` + margin-left: 10px; +`; + +// render helpers +const getAddressInputState = (address: string, addressErrors: string, addressWarnings: string): string => { + let state = ''; + if (address && !addressErrors) { + state = 'success'; + } + if (addressWarnings && !addressErrors) { + state = 'warning'; + } + if (addressErrors) { + state = 'error'; + } + return state; +}; + +const getAmountInputState = (amountErrors: string, amountWarnings: string): string => { + let state = ''; + if (amountWarnings && !amountErrors) { + state = 'warning'; + } + if (amountErrors) { + state = 'error'; + } + return state; +}; + +const getTokensSelectData = (tokens: Array, accountNetwork: any): Array<{ value: string, label: string }> => { + const tokensSelectData: Array<{ value: string, label: string }> = tokens.map(t => ({ value: t.symbol, label: t.symbol })); + tokensSelectData.unshift({ value: accountNetwork.symbol, label: accountNetwork.symbol }); + + return tokensSelectData; +}; + +// stateless component +const AccountSend = (props: Props) => { + const device = props.wallet.selectedDevice; + const { + account, + network, + discovery, + tokens, + shouldRender, + loader, + } = props.selectedAccount; + const { + address, + amount, + setMax, + networkSymbol, + currency, + feeLevels, + selectedFeeLevel, + gasPriceNeedsUpdate, + total, + errors, + warnings, + infos, + sending, + advanced, + } = props.sendForm; + + const { + toggleAdvanced, + onAddressChange, + onAmountChange, + onSetMax, + onCurrencyChange, + onFeeLevelChange, + updateFeeLevels, + onSend, + } = props.sendFormActions; + const { type, title, message } = loader; + if (!device || !account || !discovery || !network || !shouldRender) return ; + + const isCurrentCurrencyToken = networkSymbol !== currency; + + let selectedTokenBalance = 0; + const selectedToken = tokens.find(t => t.symbol === currency); + if (selectedToken) { + selectedTokenBalance = selectedToken.balance; + } + + let isSendButtonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending; + let sendButtonText: string = 'Send'; + if (networkSymbol !== currency && amount.length > 0 && !errors.amount) { + sendButtonText += ` ${amount} ${currency.toUpperCase()}`; + } else if (networkSymbol === currency && total !== '0') { + sendButtonText += ` ${total} ${network.symbol}`; + } + + if (!device.connected) { + sendButtonText = 'Device is not connected'; + isSendButtonDisabled = true; + } else if (!device.available) { + sendButtonText = 'Device is unavailable'; + isSendButtonDisabled = true; + } else if (!discovery.completed) { + sendButtonText = 'Loading accounts'; + isSendButtonDisabled = true; + } + + const tokensSelectData = getTokensSelectData(tokens, network); + const tokensSelectValue = tokensSelectData.find(t => t.value === currency); + const isAdvancedSettingsHidden = !advanced; + + return ( + + +

Send Ethereum or tokens

+ + onAddressChange(event.target.value)} + /> + + + + + Amount + {(isCurrentCurrencyToken && selectedToken) && ( + You have: {selectedTokenBalance} {selectedToken.symbol} + )} + + )} + value={amount} + onChange={event => onAmountChange(event.target.value)} + bottomText={errors.amount || warnings.amount || infos.amount} + sideAddons={[ + ( + onSetMax()} + isActive={setMax} + > + {!setMax && ( + + )} + {setMax && ( + + )} + Set max + + ), + ( + + ), + ]} + /> + + + + + Fee + {gasPriceNeedsUpdate && ( + + + Recommended fees updated. Click here to use them + + )} + + onAddressChange(event.target.value)} + /> + + + + + Amount + {(isCurrentCurrencyToken && selectedToken) && ( + You have: {selectedTokenBalance} {selectedToken.symbol} + )} + + )} + value={amount} + onChange={event => onAmountChange(event.target.value)} + bottomText={errors.amount || warnings.amount || infos.amount} + sideAddons={[ + ( + onSetMax()} + isActive={setMax} + > + {!setMax && ( + + )} + {setMax && ( + + )} + Set max + + ), + ( + + ), + ]} + /> + + + + + Fee + {gasPriceNeedsUpdate && ( + + + Recommended fees updated. Click here to use them + + )} + + { autoCorrect="off" autoCapitalize="off" spellCheck="false" - topLabel={( - - Amount - {(isCurrentCurrencyToken && selectedToken) && ( - You have: {selectedTokenBalance} {selectedToken.symbol} - )} - - )} value={amount} onChange={event => onAmountChange(event.target.value)} bottomText={errors.amount || warnings.amount || infos.amount} @@ -326,16 +291,6 @@ const AccountSend = (props: Props) => { Fee - {gasPriceNeedsUpdate && ( - - - Recommended fees updated. Click here to use them - - )} ( - -

{option.value}

-

{option.label}

-
- )} - /> -
- From 90cc406773bf7d5dba8263cddd8e7a1c8ead82f3 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 3 Dec 2018 20:22:49 +0100 Subject: [PATCH 38/72] fix pending tx for ethereum networkType --- src/actions/ethereum/SendFormActions.js | 19 +++++++++- src/actions/ripple/BlockchainActions.js | 2 ++ src/reducers/DiscoveryReducer.js | 2 ++ src/reducers/PendingTxReducer.js | 48 +++++++------------------ src/reducers/utils/index.js | 4 +-- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/actions/ethereum/SendFormActions.js b/src/actions/ethereum/SendFormActions.js index 1f29b2ac..f2bc5261 100644 --- a/src/actions/ethereum/SendFormActions.js +++ b/src/actions/ethereum/SendFormActions.js @@ -6,6 +6,7 @@ import BigNumber from 'bignumber.js'; import * as ACCOUNT from 'actions/constants/account'; import * as NOTIFICATION from 'actions/constants/notification'; import * as SEND from 'actions/constants/send'; +import * as PENDING from 'actions/constants/pendingTx'; import * as WEB3 from 'actions/constants/web3'; import { initialState } from 'reducers/SendFormEthereumReducer'; import { findToken } from 'reducers/TokensReducer'; @@ -480,7 +481,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge const currentState: State = getState().sendFormEthereum; const isToken: boolean = currentState.currency !== currentState.networkSymbol; - const pendingNonce: number = reducerUtils.getPendingNonce(pending); + const pendingNonce: number = reducerUtils.getPendingSequence(pending); const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce; const txData = await dispatch(prepareEthereumTx({ @@ -553,6 +554,22 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge txData, }); + dispatch({ + type: PENDING.ADD, + payload: { + type: 'send', + deviceState: account.deviceState, + sequence: nonce, + hash: txid, + network: account.network, + address: account.address, + currency: currentState.currency, + amount: currentState.amount, + total: currentState.total, + fee: '0', // TODO: calculate fee + }, + }); + // clear session storage dispatch(SessionStorageActions.clear()); diff --git a/src/actions/ripple/BlockchainActions.js b/src/actions/ripple/BlockchainActions.js index 44a6b37e..d2214a89 100644 --- a/src/actions/ripple/BlockchainActions.js +++ b/src/actions/ripple/BlockchainActions.js @@ -44,6 +44,8 @@ export const onNotification = (payload: $ElementType { instance.hdKey = hdKey; instance.publicKey = action.publicKey; instance.chainCode = action.chainCode; + + instance.basePath = action.basePath; } const newState: State = [...state]; diff --git a/src/reducers/PendingTxReducer.js b/src/reducers/PendingTxReducer.js index edf7ee8a..ed1f8bdd 100644 --- a/src/reducers/PendingTxReducer.js +++ b/src/reducers/PendingTxReducer.js @@ -1,12 +1,13 @@ /* @flow */ import * as CONNECT from 'actions/constants/TrezorConnect'; import * as PENDING from 'actions/constants/pendingTx'; -import * as SEND from 'actions/constants/send'; -import type { TrezorDevice, Action } from 'flowtype'; +import type { Action } from 'flowtype'; export type PendingTx = { +type: 'send' | 'recv', + +deviceState: string, + +sequence: number, +hash: string, +network: string, +address: string, @@ -21,35 +22,15 @@ export type State = Array; const initialState: State = []; -// const add = (state: State, action: SendTxAction): State => { -// const newState = [...state]; -// newState.push({ -// type: 'send', -// id: action.txid, -// network: action.account.network, -// address: action.account.address, -// deviceState: action.account.deviceState, - -// currency: action.selectedCurrency, -// amount: action.amount, -// total: action.total, -// tx: action.tx, -// nonce: action.nonce, -// rejected: false, -// }); -// return newState; -// }; - - -const addFromNotification = (state: State, payload: PendingTx): State => { +const add = (state: State, payload: PendingTx): State => { const newState = [...state]; newState.push(payload); return newState; }; -//const clear = (state: State, device: TrezorDevice): State => state.filter(tx => tx.deviceState !== device.state); +const removeByDeviceState = (state: State, deviceState: ?string): State => state.filter(tx => tx.deviceState !== deviceState); -const remove = (state: State, hash: string): State => state.filter(tx => tx.hash !== hash); +const removeByHash = (state: State, hash: string): State => state.filter(tx => tx.hash !== hash); const reject = (state: State, hash: string): State => state.map((tx) => { if (tx.hash === hash && !tx.rejected) { @@ -60,19 +41,16 @@ const reject = (state: State, hash: 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 CONNECT.FORGET: - // case CONNECT.FORGET_SINGLE: - // case CONNECT.FORGET_SILENT: - // case CONNECT.RECEIVE_WALLET_TYPE: - // return clear(state, action.device); + case CONNECT.FORGET: + case CONNECT.FORGET_SINGLE: + case CONNECT.FORGET_SILENT: + case CONNECT.RECEIVE_WALLET_TYPE: + return removeByDeviceState(state, action.device.state); case PENDING.ADD: - return addFromNotification(state, action.payload); + return add(state, action.payload); case PENDING.TX_RESOLVED: - return remove(state, action.hash); + return removeByHash(state, action.hash); case PENDING.TX_REJECTED: return reject(state, action.hash); diff --git a/src/reducers/utils/index.js b/src/reducers/utils/index.js index c56cebdd..078535aa 100644 --- a/src/reducers/utils/index.js +++ b/src/reducers/utils/index.js @@ -89,9 +89,9 @@ export const getAccountPendingTx = (pending: Array, account: ?Account return pending.filter(p => p.network === a.network && p.address === a.address); }; -export const getPendingNonce = (pending: Array): number => pending.reduce((value: number, tx: PendingTx) => { +export const getPendingSequence = (pending: Array): number => pending.reduce((value: number, tx: PendingTx) => { if (tx.rejected) return value; - return Math.max(value, tx.nonce + 1); + return Math.max(value, tx.sequence + 1); }, 0); export const getPendingAmount = (pending: Array, currency: string, token: boolean = false): BigNumber => pending.reduce((value: BigNumber, tx: PendingTx) => { From bfebef73c77073c117c091fb2dc467b628f65d04 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 3 Dec 2018 21:01:33 +0100 Subject: [PATCH 39/72] ripple/SendFormValidations --- src/actions/ripple/SendFormActions.js | 14 +- .../ripple/SendFormValidationActions.js | 280 ++---------------- .../Wallet/views/Account/Send/ripple/index.js | 2 - 3 files changed, 40 insertions(+), 256 deletions(-) diff --git a/src/actions/ripple/SendFormActions.js b/src/actions/ripple/SendFormActions.js index 8cf9e2aa..df241259 100644 --- a/src/actions/ripple/SendFormActions.js +++ b/src/actions/ripple/SendFormActions.js @@ -4,7 +4,7 @@ import Link from 'components/Link'; import TrezorConnect from 'trezor-connect'; import * as NOTIFICATION from 'actions/constants/notification'; import * as SEND from 'actions/constants/send'; -import * as WEB3 from 'actions/constants/web3'; +import * as BLOCKCHAIN from 'actions/constants/blockchain'; import { initialState } from 'reducers/SendFormRippleReducer'; import * as reducerUtils from 'reducers/utils'; import { fromDecimalAmount } from 'utils/formatUtils'; @@ -57,8 +57,8 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction = // handle gasPrice update from backend // recalculate fee levels if needed - if (action.type === WEB3.GAS_PRICE_UPDATED) { - dispatch(ValidationActions.onGasPriceUpdated(action.network, action.gasPrice)); + if (action.type === BLOCKCHAIN.UPDATE_FEE) { + // dispatch(ValidationActions.onGasPriceUpdated(action.network, action.gasPrice)); return; } @@ -107,7 +107,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS return; } - const feeLevels = ValidationActions.getFeeLevels(network.symbol, '1', '1'); + const feeLevels = ValidationActions.getFeeLevels(network.symbol); const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel); dispatch({ @@ -191,6 +191,9 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge if (!account || !network) return; + const blockchain = getState().blockchain.find(b => b.shortcut === account.network); + if (!blockchain) return; + const currentState: State = getState().sendFormRipple; const amount = fromDecimalAmount(currentState.amount, 6); @@ -203,8 +206,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge useEmptyPassphrase: selected.useEmptyPassphrase, path: account.addressPath, transaction: { - // fee: '12', - fee: '10', // Fee must be in the range of 10 to 10,000 drops + fee: blockchain.fee, // Fee must be in the range of 10 to 10,000 drops flags: 0x80000000, sequence: account.sequence, payment: { diff --git a/src/actions/ripple/SendFormValidationActions.js b/src/actions/ripple/SendFormValidationActions.js index 3668cb2a..2b4f194d 100644 --- a/src/actions/ripple/SendFormValidationActions.js +++ b/src/actions/ripple/SendFormValidationActions.js @@ -1,12 +1,8 @@ /* @flow */ import BigNumber from 'bignumber.js'; -import EthereumjsUtil from 'ethereumjs-util'; -import EthereumjsUnits from 'ethereumjs-units'; -import { findToken } from 'reducers/TokensReducer'; import { findDevice, getPendingAmount } from 'reducers/utils'; -import * as SEND from 'actions/constants/send'; -import * as ethUtils from 'utils/ethUtils'; +import { toDecimalAmount } from 'utils/formatUtils'; import type { Dispatch, @@ -16,65 +12,9 @@ import type { import type { State, FeeLevel } from 'reducers/SendFormRippleReducer'; // general regular expressions -const RIPPLE_ADDRESS_RE = new RegExp('^r[1-9A-HJ-NP-Za-km-z]{25,34}$'); +const XRP_ADDRESS_RE = new RegExp('^r[1-9A-HJ-NP-Za-km-z]{25,34}$'); const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$'); -const UPPERCASE_RE = new RegExp('^(.*[A-Z].*)$'); -const ABS_RE = new RegExp('^[0-9]+$'); -const ETH_18_RE = new RegExp('^(0|0\\.([0-9]{0,18})?|[1-9][0-9]*\\.?([0-9]{0,18})?|\\.[0-9]{0,18})$'); -const dynamicRegexp = (decimals: number): RegExp => { - if (decimals > 0) { - return new RegExp(`^(0|0\\.([0-9]{0,${decimals}})?|[1-9][0-9]*\\.?([0-9]{0,${decimals}})?|\\.[0-9]{1,${decimals}})$`); - } - return ABS_RE; -}; - -/* -* Called from SendFormActions.observe -* Reaction for WEB3.GAS_PRICE_UPDATED action -*/ -export const onGasPriceUpdated = (network: string, gasPrice: string): PayloadAction => (dispatch: Dispatch, getState: GetState): void => { - // testing random data - // function getRandomInt(min, max) { - // return Math.floor(Math.random() * (max - min + 1)) + min; - // } - // const newPrice = getRandomInt(10, 50).toString(); - - const state = getState().sendFormRipple; - if (network === state.networkSymbol) return; - - // check if new price is different then currently recommended - const newPrice: string = EthereumjsUnits.convert(gasPrice, 'wei', 'gwei'); - - if (newPrice !== state.recommendedGasPrice) { - if (!state.untouched) { - // if there is a transaction draft let the user know - // and let him update manually - dispatch({ - type: SEND.CHANGE, - state: { - ...state, - gasPriceNeedsUpdate: true, - recommendedGasPrice: newPrice, - }, - }); - } else { - // automatically update feeLevels and gasPrice - const feeLevels = getFeeLevels(state.networkSymbol, newPrice, state.gasLimit); - const selectedFeeLevel = getSelectedFeeLevel(feeLevels, state.selectedFeeLevel); - dispatch({ - type: SEND.CHANGE, - state: { - ...state, - gasPriceNeedsUpdate: false, - recommendedGasPrice: newPrice, - gasPrice: selectedFeeLevel.gasPrice, - feeLevels, - selectedFeeLevel, - }, - }); - } - } -}; +const XRP_6_RE = new RegExp('^(0|0\\.([0-9]{0,6})?|[1-9][0-9]*\\.?([0-9]{0,6})?|\\.[0-9]{0,6})$'); /* * Recalculate amount, total and fees @@ -88,65 +28,53 @@ export const validation = (): PayloadAction => (dispatch: Dispatch, getSt state.warnings = {}; state.infos = {}; state = dispatch(recalculateTotalAmount(state)); - state = dispatch(updateCustomFeeLabel(state)); state = dispatch(addressValidation(state)); state = dispatch(addressLabel(state)); state = dispatch(amountValidation(state)); - state = dispatch(gasLimitValidation(state)); - state = dispatch(gasPriceValidation(state)); - state = dispatch(nonceValidation(state)); - state = dispatch(dataValidation(state)); return state; }; -export const recalculateTotalAmount = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { +const recalculateTotalAmount = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { const { account, - tokens, pending, } = getState().selectedAccount; if (!account) return $state; + const blockchain = getState().blockchain.find(b => b.shortcut === account.network); + if (!blockchain) return $state; + const fee = toDecimalAmount(blockchain.fee, 6); + const state = { ...$state }; if (state.setMax) { const pendingAmount = getPendingAmount(pending, state.networkSymbol, false); const b = new BigNumber(account.balance).minus(pendingAmount); - state.amount = calculateMaxAmount(b, state.gasPrice, state.gasLimit); + state.amount = calculateMaxAmount(b, fee); } - state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit); - return state; -}; - -export const updateCustomFeeLabel = ($state: State): PayloadAction => (): State => { - const state = { ...$state }; - if ($state.selectedFeeLevel.value === 'Custom') { - state.selectedFeeLevel = { - ...state.selectedFeeLevel, - gasPrice: state.gasPrice, - label: `${calculateFee(state.gasPrice, state.gasLimit)} ${state.networkSymbol}`, - }; - } + state.total = calculateTotal(state.amount, fee); return state; }; /* * Address value validation */ -export const addressValidation = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { +const addressValidation = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { const state = { ...$state }; if (!state.touched.address) return state; - const { network } = getState().selectedAccount; - if (!network) return state; + const { account, network } = getState().selectedAccount; + if (!account || !network) return state; const { address } = state; if (address.length < 1) { state.errors.address = 'Address is not set'; - } else if (!address.match(RIPPLE_ADDRESS_RE)) { + } else if (!address.match(XRP_ADDRESS_RE)) { state.errors.address = 'Address is not valid'; + } else if (address.toLowerCase() === account.address.toLowerCase()) { + state.errors.address = 'Cannot send to myself'; } return state; }; @@ -154,7 +82,7 @@ export const addressValidation = ($state: State): PayloadAction => (dispa /* * Address label assignation */ -export const addressLabel = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { +const addressLabel = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { const state = { ...$state }; if (!state.touched.address || state.errors.address) return state; @@ -192,13 +120,12 @@ export const addressLabel = ($state: State): PayloadAction => (dispatch: /* * Amount value validation */ -export const amountValidation = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { +const amountValidation = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { const state = { ...$state }; if (!state.touched.amount) return state; const { account, - tokens, pending, } = getState().selectedAccount; if (!account) return state; @@ -209,25 +136,10 @@ export const amountValidation = ($state: State): PayloadAction => (dispat } else if (amount.length > 0 && !amount.match(NUMBER_RE)) { state.errors.amount = 'Amount is not a number'; } else { - const isToken: boolean = state.currency !== state.networkSymbol; - const pendingAmount: BigNumber = getPendingAmount(pending, state.currency, isToken); + const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol); - if (isToken) { - const token = findToken(tokens, account.address, state.currency, account.deviceState); - if (!token) return state; - const decimalRegExp = dynamicRegexp(parseInt(token.decimals, 0)); - - if (!state.amount.match(decimalRegExp)) { - state.errors.amount = `Maximum ${token.decimals} decimals allowed`; - } else if (new BigNumber(state.total).greaterThan(account.balance)) { - state.errors.amount = `Not enough ${state.networkSymbol} to cover transaction fee`; - } else if (new BigNumber(state.amount).greaterThan(new BigNumber(token.balance).minus(pendingAmount))) { - state.errors.amount = 'Not enough funds'; - } else if (new BigNumber(state.amount).lessThanOrEqualTo('0')) { - state.errors.amount = 'Amount is too low'; - } - } else if (!state.amount.match(ETH_18_RE)) { - state.errors.amount = 'Maximum 18 decimals allowed'; + if (!state.amount.match(XRP_6_RE)) { + state.errors.amount = 'Maximum 6 decimals allowed'; } else if (new BigNumber(state.total).greaterThan(new BigNumber(account.balance).minus(pendingAmount))) { state.errors.amount = 'Not enough funds'; } @@ -235,121 +147,21 @@ export const amountValidation = ($state: State): PayloadAction => (dispat return state; }; -/* -* Gas limit value validation -*/ -export const gasLimitValidation = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { - const state = { ...$state }; - if (!state.touched.gasLimit) return state; - - const { - network, - } = getState().selectedAccount; - if (!network) return state; - - const { gasLimit } = state; - if (gasLimit.length < 1) { - state.errors.gasLimit = 'Gas limit is not set'; - } else if (gasLimit.length > 0 && !gasLimit.match(NUMBER_RE)) { - state.errors.gasLimit = 'Gas limit is not a number'; - } else { - const gl: BigNumber = new BigNumber(gasLimit); - if (gl.lessThan(1)) { - state.errors.gasLimit = 'Gas limit is too low'; - } else if (gl.lessThan(state.currency !== state.networkSymbol ? network.defaultGasLimitTokens : network.defaultGasLimit)) { - state.warnings.gasLimit = 'Gas limit is below recommended'; - } - } - return state; -}; - -/* -* Gas price value validation -*/ -export const gasPriceValidation = ($state: State): PayloadAction => (): State => { - const state = { ...$state }; - if (!state.touched.gasPrice) return state; - - const { gasPrice } = state; - if (gasPrice.length < 1) { - state.errors.gasPrice = 'Gas price is not set'; - } else if (gasPrice.length > 0 && !gasPrice.match(NUMBER_RE)) { - state.errors.gasPrice = 'Gas price is not a number'; - } else { - const gp: BigNumber = new BigNumber(gasPrice); - if (gp.greaterThan(1000)) { - state.warnings.gasPrice = 'Gas price is too high'; - } else if (gp.lessThanOrEqualTo('0')) { - state.errors.gasPrice = 'Gas price is too low'; - } - } - return state; -}; - -/* -* Nonce value validation -*/ -export const nonceValidation = ($state: State): PayloadAction => (dispatch: Dispatch, getState: GetState): State => { - const state = { ...$state }; - if (!state.touched.nonce) return state; - - const { - account, - } = getState().selectedAccount; - if (!account) return state; - - const { nonce } = state; - if (nonce.length < 1) { - state.errors.nonce = 'Nonce is not set'; - } else if (!nonce.match(ABS_RE)) { - state.errors.nonce = 'Nonce is not a valid number'; - } else { - const n: BigNumber = new BigNumber(nonce); - if (n.lessThan(account.nonce)) { - state.warnings.nonce = 'Nonce is lower than recommended'; - } else if (n.greaterThan(account.nonce)) { - state.warnings.nonce = 'Nonce is greater than recommended'; - } - } - return state; -}; - -/* -* Gas price value validation -*/ -export const dataValidation = ($state: State): PayloadAction => (): State => { - const state = { ...$state }; - if (!state.touched.data || state.data.length === 0) return state; - if (!ethUtils.isHex(state.data)) { - state.errors.data = 'Data is not valid hexadecimal'; - } - return state; -}; - /* * UTILITIES */ -export const calculateFee = (gasPrice: string, gasLimit: string): string => { +const calculateTotal = (amount: string, fee: string): string => { try { - return EthereumjsUnits.convert(new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether'); + return new BigNumber(amount).plus(fee).toString(10); } catch (error) { return '0'; } }; -export const calculateTotal = (amount: string, gasPrice: string, gasLimit: string): string => { - try { - return new BigNumber(amount).plus(calculateFee(gasPrice, gasLimit)).toString(10); - } catch (error) { - return '0'; - } -}; - -export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimit: string): string => { +const calculateMaxAmount = (balance: BigNumber, fee: string): string => { try { // TODO - minus pendings - const fee = calculateFee(gasPrice, gasLimit); const max = balance.minus(fee); if (max.lessThan(0)) return '0'; return max.toString(10); @@ -358,42 +170,14 @@ export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimi } }; -export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string, selected?: FeeLevel): Array => { - const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice; - const quarter: BigNumber = price.dividedBy(4); - const high: string = price.plus(quarter.times(2)).toString(10); - const low: string = price.minus(quarter.times(2)).toString(10); +export const getFeeLevels = (shortcut: string): Array => ([ + { + value: 'Normal', + gasPrice: '1', + label: `1 ${shortcut}`, + }, +]); - const customLevel: FeeLevel = selected && selected.value === 'Custom' ? { - value: 'Custom', - gasPrice: selected.gasPrice, - // label: `${ calculateFee(gasPrice, gasLimit) } ${ symbol }` - label: `${calculateFee(selected.gasPrice, gasLimit)} ${symbol}`, - } : { - value: 'Custom', - gasPrice: low, - label: '', - }; - - return [ - { - value: 'High', - gasPrice: high, - label: `${calculateFee(high, gasLimit)} ${symbol}`, - }, - { - value: 'Normal', - gasPrice: gasPrice.toString(), - label: `${calculateFee(price.toString(10), gasLimit)} ${symbol}`, - }, - { - value: 'Low', - gasPrice: low, - label: `${calculateFee(low, gasLimit)} ${symbol}`, - }, - customLevel, - ]; -}; export const getSelectedFeeLevel = (feeLevels: Array, selected: FeeLevel): FeeLevel => { const { value } = selected; @@ -404,4 +188,4 @@ export const getSelectedFeeLevel = (feeLevels: Array, selected: FeeLev selectedFeeLevel = feeLevels.find(f => f.value === 'Normal'); } return selectedFeeLevel || selected; -}; \ No newline at end of file +}; diff --git a/src/views/Wallet/views/Account/Send/ripple/index.js b/src/views/Wallet/views/Account/Send/ripple/index.js index e63d9eca..ea07f40f 100644 --- a/src/views/Wallet/views/Account/Send/ripple/index.js +++ b/src/views/Wallet/views/Account/Send/ripple/index.js @@ -160,8 +160,6 @@ const AccountSend = (props: Props) => { isSendButtonDisabled = true; } - isSendButtonDisabled = false; - const tokensSelectData: Array<{ value: string, label: string }> = [{ value: network.symbol, label: network.symbol }]; const tokensSelectValue = tokensSelectData[0]; const isAdvancedSettingsHidden = !advanced; From 9b7987e951fcfe75a2cdcdc4747049eca1a39c43 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 3 Dec 2018 21:20:48 +0100 Subject: [PATCH 40/72] add availableBalance to Account.update action --- src/actions/Web3Actions.js | 6 +++++- src/actions/ripple/BlockchainActions.js | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/actions/Web3Actions.js b/src/actions/Web3Actions.js index 5439e6c2..94030727 100644 --- a/src/actions/Web3Actions.js +++ b/src/actions/Web3Actions.js @@ -196,7 +196,11 @@ export const updateAccount = (account: Account, newAccount: EthereumAccount, net 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, + ...account, + ...newAccount, + nonce, + balance: EthereumjsUnits.convert(balance, 'wei', 'ether'), + availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'), })); // update tokens for this account diff --git a/src/actions/ripple/BlockchainActions.js b/src/actions/ripple/BlockchainActions.js index d2214a89..14f7e020 100644 --- a/src/actions/ripple/BlockchainActions.js +++ b/src/actions/ripple/BlockchainActions.js @@ -4,6 +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 type { BlockchainNotification } from 'trezor-connect'; import type { @@ -75,7 +76,8 @@ export const onNotification = (payload: $ElementType Date: Mon, 3 Dec 2018 21:26:52 +0100 Subject: [PATCH 41/72] split Receive component into coin specific --- .../Receive/{ => ethereum}/Container.js | 0 .../views/Account/Receive/ethereum/index.js | 180 ++++++++++++++++ .../Wallet/views/Account/Receive/index.js | 194 ++---------------- .../views/Account/Receive/ripple/Container.js | 36 ++++ .../views/Account/Receive/ripple/index.js | 180 ++++++++++++++++ src/views/index.js | 2 +- 6 files changed, 418 insertions(+), 174 deletions(-) rename src/views/Wallet/views/Account/Receive/{ => ethereum}/Container.js (100%) create mode 100644 src/views/Wallet/views/Account/Receive/ethereum/index.js create mode 100644 src/views/Wallet/views/Account/Receive/ripple/Container.js create mode 100644 src/views/Wallet/views/Account/Receive/ripple/index.js diff --git a/src/views/Wallet/views/Account/Receive/Container.js b/src/views/Wallet/views/Account/Receive/ethereum/Container.js similarity index 100% rename from src/views/Wallet/views/Account/Receive/Container.js rename to src/views/Wallet/views/Account/Receive/ethereum/Container.js diff --git a/src/views/Wallet/views/Account/Receive/ethereum/index.js b/src/views/Wallet/views/Account/Receive/ethereum/index.js new file mode 100644 index 00000000..b0666921 --- /dev/null +++ b/src/views/Wallet/views/Account/Receive/ethereum/index.js @@ -0,0 +1,180 @@ +/* @flow */ +import React from 'react'; +import { QRCode } from 'react-qr-svg'; +import styled from 'styled-components'; +import media from 'styled-media-query'; + +import { H2 } from 'components/Heading'; +import Button from 'components/Button'; +import Icon from 'components/Icon'; +import Tooltip from 'components/Tooltip'; +import Input from 'components/inputs/Input'; + +import ICONS from 'config/icons'; +import colors from 'config/colors'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; + +import Content from 'views/Wallet/components/Content'; +import VerifyAddressTooltip from '../components/VerifyAddressTooltip'; + +import type { Props } from './Container'; + +const Label = styled.div` + padding: 25px 0 5px 0; + color: ${colors.TEXT_SECONDARY}; +`; + +const AddressWrapper = styled.div` + display: flex; + flex-wrap: wrap; + flex-direction: row; +`; + +const StyledQRCode = styled(QRCode)` + padding: 15px; + margin-top: 0 25px; + border: 1px solid ${colors.BODY}; +`; + +const ShowAddressButton = styled(Button)` + min-width: 195px; + padding: 0; + white-space: nowrap; + display: flex; + height: 40px; + align-items: center; + justify-content: center; + + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + ${media.lessThan('795px')` + margin-top: 10px; + `} +`; + +const ShowAddressIcon = styled(Icon)` + margin-right: 7px; + position: relative; + top: 2px; +`; + +const EyeButton = styled(Button)` + z-index: 10001; + padding: 0; + width: 30px; + background: white; + top: 5px; + position: absolute; + right: 10px; + + &:hover { + background: white; + } +`; + +const Row = styled.div` + display: flex; + width: 100%; + + ${media.lessThan('795px')` + flex-direction: column; + `} +`; + +const QrWrapper = styled.div` + display: flex; + flex-direction: column; +`; + +const AccountReceive = (props: Props) => { + const device = props.wallet.selectedDevice; + const { + account, + discovery, + shouldRender, + loader, + } = props.selectedAccount; + const { type, title, message } = loader; + if (!device || !account || !discovery || !shouldRender) return ; + + const { + addressVerified, + addressUnverified, + } = props.receive; + + const isAddressVerifying = props.modal.context === CONTEXT_DEVICE && props.modal.windowType === 'ButtonRequest_Address'; + const isAddressHidden = !isAddressVerifying && !addressVerified && !addressUnverified; + + let address = `${account.address.substring(0, 20)}...`; + if (addressVerified || addressUnverified || isAddressVerifying) { + ({ address } = account); + } + + return ( + + +

Receive Ethereum or tokens

+ + + + + + Check address on your Trezor +
+ ) : null} + icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( + + )} + > + props.showAddress(account.addressPath)}> + + + + )} + /> + {!(addressVerified || addressUnverified) && ( + props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> + Show full address + + )} + + {(addressVerified || addressUnverified) && !isAddressVerifying && ( + + + + + )} + +
+
+ ); +}; + +export default AccountReceive; diff --git a/src/views/Wallet/views/Account/Receive/index.js b/src/views/Wallet/views/Account/Receive/index.js index 755fcb43..60919d3f 100644 --- a/src/views/Wallet/views/Account/Receive/index.js +++ b/src/views/Wallet/views/Account/Receive/index.js @@ -1,180 +1,28 @@ /* @flow */ import React from 'react'; -import { QRCode } from 'react-qr-svg'; -import styled from 'styled-components'; -import media from 'styled-media-query'; +import { connect } from 'react-redux'; -import { H2 } from 'components/Heading'; -import Button from 'components/Button'; -import Icon from 'components/Icon'; -import Tooltip from 'components/Tooltip'; -import Input from 'components/inputs/Input'; +import type { State } from 'flowtype'; +import EthereumTypeReceiveForm from './ethereum/Container'; +import RippleTypeReceiveForm from './ripple/Container'; -import ICONS from 'config/icons'; -import colors from 'config/colors'; -import { CONTEXT_DEVICE } from 'actions/constants/modal'; +export type BaseProps = { + selectedAccount: $ElementType, +} -import Content from 'views/Wallet/components/Content'; -import VerifyAddressTooltip from './components/VerifyAddressTooltip'; +// return container for requested network type +export default connect((state: State): BaseProps => ({ + selectedAccount: state.selectedAccount, +}), null)((props) => { + const { network } = props.selectedAccount; + if (!network) return null; -import type { Props } from './Container'; - -const Label = styled.div` - padding: 25px 0 5px 0; - color: ${colors.TEXT_SECONDARY}; -`; - -const AddressWrapper = styled.div` - display: flex; - flex-wrap: wrap; - flex-direction: row; -`; - -const StyledQRCode = styled(QRCode)` - padding: 15px; - margin-top: 0 25px; - border: 1px solid ${colors.BODY}; -`; - -const ShowAddressButton = styled(Button)` - min-width: 195px; - padding: 0; - white-space: nowrap; - display: flex; - height: 40px; - align-items: center; - justify-content: center; - - border-top-left-radius: 0; - border-bottom-left-radius: 0; - - ${media.lessThan('795px')` - margin-top: 10px; - `} -`; - -const ShowAddressIcon = styled(Icon)` - margin-right: 7px; - position: relative; - top: 2px; -`; - -const EyeButton = styled(Button)` - z-index: 10001; - padding: 0; - width: 30px; - background: white; - top: 5px; - position: absolute; - right: 10px; - - &:hover { - background: white; + switch (network.type) { + case 'ethereum': + return ; + case 'ripple': + return ; + default: + return null; } -`; - -const Row = styled.div` - display: flex; - width: 100%; - - ${media.lessThan('795px')` - flex-direction: column; - `} -`; - -const QrWrapper = styled.div` - display: flex; - flex-direction: column; -`; - -const AccountReceive = (props: Props) => { - const device = props.wallet.selectedDevice; - const { - account, - discovery, - shouldRender, - loader, - } = props.selectedAccount; - const { type, title, message } = loader; - if (!device || !account || !discovery || !shouldRender) return ; - - const { - addressVerified, - addressUnverified, - } = props.receive; - - const isAddressVerifying = props.modal.context === CONTEXT_DEVICE && props.modal.windowType === 'ButtonRequest_Address'; - const isAddressHidden = !isAddressVerifying && !addressVerified && !addressUnverified; - - let address = `${account.address.substring(0, 20)}...`; - if (addressVerified || addressUnverified || isAddressVerifying) { - ({ address } = account); - } - - return ( - - -

Receive Ethereum or tokens

- - - - - - Check address on your Trezor -
- ) : null} - icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( - - )} - > - props.showAddress(account.addressPath)}> - - - - )} - /> - {!(addressVerified || addressUnverified) && ( - props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> - Show full address - - )} - - {(addressVerified || addressUnverified) && !isAddressVerifying && ( - - - - - )} - - -
- ); -}; - -export default AccountReceive; +}); \ No newline at end of file diff --git a/src/views/Wallet/views/Account/Receive/ripple/Container.js b/src/views/Wallet/views/Account/Receive/ripple/Container.js new file mode 100644 index 00000000..27212056 --- /dev/null +++ b/src/views/Wallet/views/Account/Receive/ripple/Container.js @@ -0,0 +1,36 @@ +/* @flow */ +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; + +import { showAddress } from 'actions/ReceiveActions'; +import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; +import type { State, Dispatch } from 'flowtype'; +import Receive from './index'; + +type OwnProps = { } + +type StateProps = { + selectedAccount: $ElementType, + receive: $ElementType, + modal: $ElementType, + wallet: $ElementType, +} + +type DispatchProps = { + showAddress: typeof showAddress +}; + +export type Props = StateProps & DispatchProps; + +const mapStateToProps: MapStateToProps = (state: State): StateProps => ({ + selectedAccount: state.selectedAccount, + receive: state.receive, + modal: state.modal, + wallet: state.wallet, +}); + +const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + showAddress: bindActionCreators(showAddress, dispatch), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Receive); \ No newline at end of file diff --git a/src/views/Wallet/views/Account/Receive/ripple/index.js b/src/views/Wallet/views/Account/Receive/ripple/index.js new file mode 100644 index 00000000..db7974b6 --- /dev/null +++ b/src/views/Wallet/views/Account/Receive/ripple/index.js @@ -0,0 +1,180 @@ +/* @flow */ +import React from 'react'; +import { QRCode } from 'react-qr-svg'; +import styled from 'styled-components'; +import media from 'styled-media-query'; + +import { H2 } from 'components/Heading'; +import Button from 'components/Button'; +import Icon from 'components/Icon'; +import Tooltip from 'components/Tooltip'; +import Input from 'components/inputs/Input'; + +import ICONS from 'config/icons'; +import colors from 'config/colors'; +import { CONTEXT_DEVICE } from 'actions/constants/modal'; + +import Content from 'views/Wallet/components/Content'; +import VerifyAddressTooltip from '../components/VerifyAddressTooltip'; + +import type { Props } from './Container'; + +const Label = styled.div` + padding: 25px 0 5px 0; + color: ${colors.TEXT_SECONDARY}; +`; + +const AddressWrapper = styled.div` + display: flex; + flex-wrap: wrap; + flex-direction: row; +`; + +const StyledQRCode = styled(QRCode)` + padding: 15px; + margin-top: 0 25px; + border: 1px solid ${colors.BODY}; +`; + +const ShowAddressButton = styled(Button)` + min-width: 195px; + padding: 0; + white-space: nowrap; + display: flex; + height: 40px; + align-items: center; + justify-content: center; + + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + ${media.lessThan('795px')` + margin-top: 10px; + `} +`; + +const ShowAddressIcon = styled(Icon)` + margin-right: 7px; + position: relative; + top: 2px; +`; + +const EyeButton = styled(Button)` + z-index: 10001; + padding: 0; + width: 30px; + background: white; + top: 5px; + position: absolute; + right: 10px; + + &:hover { + background: white; + } +`; + +const Row = styled.div` + display: flex; + width: 100%; + + ${media.lessThan('795px')` + flex-direction: column; + `} +`; + +const QrWrapper = styled.div` + display: flex; + flex-direction: column; +`; + +const AccountReceive = (props: Props) => { + const device = props.wallet.selectedDevice; + const { + account, + discovery, + shouldRender, + loader, + } = props.selectedAccount; + const { type, title, message } = loader; + if (!device || !account || !discovery || !shouldRender) return ; + + const { + addressVerified, + addressUnverified, + } = props.receive; + + const isAddressVerifying = props.modal.context === CONTEXT_DEVICE && props.modal.windowType === 'ButtonRequest_Address'; + const isAddressHidden = !isAddressVerifying && !addressVerified && !addressUnverified; + + let address = `${account.address.substring(0, 20)}...`; + if (addressVerified || addressUnverified || isAddressVerifying) { + ({ address } = account); + } + + return ( + + +

Receive Ripple

+ + + + + + Check address on your Trezor +
+ ) : null} + icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( + + )} + > + props.showAddress(account.addressPath)}> + + + + )} + /> + {!(addressVerified || addressUnverified) && ( + props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> + Show full address + + )} + + {(addressVerified || addressUnverified) && !isAddressVerifying && ( + + + + + )} + + +
+ ); +}; + +export default AccountReceive; diff --git a/src/views/index.js b/src/views/index.js index 994effdb..6e37aef5 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -18,7 +18,7 @@ import ImportView from 'views/Landing/views/Import/Container'; import WalletContainer from 'views/Wallet'; import AccountSummary from 'views/Wallet/views/Account/Summary'; import AccountSend from 'views/Wallet/views/Account/Send'; -import AccountReceive from 'views/Wallet/views/Account/Receive/Container'; +import AccountReceive from 'views/Wallet/views/Account/Receive'; import AccountSignVerify from 'views/Wallet/views/Account/SignVerify/Container'; import WalletDashboard from 'views/Wallet/views/Dashboard'; From 695ae1717607b27fb7904a1c54157a08fc0370a0 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 3 Dec 2018 21:43:42 +0100 Subject: [PATCH 42/72] remove unnecessary SEND.TX_COMPLETE action --- src/actions/SelectedAccountActions.js | 2 -- src/actions/SendFormActions.js | 18 +++----------- src/actions/ethereum/SendFormActions.js | 32 +------------------------ src/actions/ripple/SendFormActions.js | 25 +------------------ src/services/LocalStorageService.js | 3 +-- 5 files changed, 6 insertions(+), 74 deletions(-) diff --git a/src/actions/SelectedAccountActions.js b/src/actions/SelectedAccountActions.js index 7b61eda0..8f7d9445 100644 --- a/src/actions/SelectedAccountActions.js +++ b/src/actions/SelectedAccountActions.js @@ -6,7 +6,6 @@ import * as ACCOUNT from 'actions/constants/account'; import * as DISCOVERY from 'actions/constants/discovery'; import * as TOKEN from 'actions/constants/token'; import * as PENDING from 'actions/constants/pendingTx'; -import * as SEND from 'actions/constants/send'; import * as reducerUtils from 'reducers/utils'; @@ -184,7 +183,6 @@ const getAccountNotification = (state: State, selectedAccount: SelectedAccountSt const actions = [ LOCATION_CHANGE, ...Object.values(BLOCKCHAIN).filter(v => typeof v === 'string'), - SEND.TX_COMPLETE, WALLET.SET_SELECTED_DEVICE, WALLET.UPDATE_SELECTED_DEVICE, ...Object.values(ACCOUNT).filter(v => typeof v === 'string' && v !== ACCOUNT.UPDATE_SELECTED_ACCOUNT && v !== ACCOUNT.DISPOSE), // exported values got unwanted "__esModule: true" as first element diff --git a/src/actions/SendFormActions.js b/src/actions/SendFormActions.js index bfef42dd..187b09cb 100644 --- a/src/actions/SendFormActions.js +++ b/src/actions/SendFormActions.js @@ -12,24 +12,10 @@ import type { } from 'flowtype'; import type { State as EthereumState } from 'reducers/SendFormEthereumReducer'; import type { State as RippleState } from 'reducers/SendFormRippleReducer'; -import type { Account } from 'reducers/AccountsReducer'; import * as EthereumSendFormActions from './ethereum/SendFormActions'; import * as RippleSendFormActions from './ripple/SendFormActions'; -export type SendTxAction = { - type: typeof SEND.TX_COMPLETE, - //networkType: 'ethereum', - account: Account, - selectedCurrency: string, - amount: string, - total: string, - tx: any, - nonce: number, - txid: string, - txData: any, -} - export type SendFormAction = { type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE, networkType: 'ethereum', @@ -40,7 +26,9 @@ export type SendFormAction = { state: RippleState, } | { type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR, -} | SendTxAction; +} | { + type: typeof SEND.TX_COMPLETE, +}; // list of all actions which has influence on "sendForm" reducer diff --git a/src/actions/ethereum/SendFormActions.js b/src/actions/ethereum/SendFormActions.js index f2bc5261..60d02132 100644 --- a/src/actions/ethereum/SendFormActions.js +++ b/src/actions/ethereum/SendFormActions.js @@ -23,32 +23,12 @@ import type { TrezorDevice, } from 'flowtype'; import type { State, FeeLevel } from 'reducers/SendFormEthereumReducer'; -import type { Account } from 'reducers/AccountsReducer'; import * as SessionStorageActions from '../SessionStorageActions'; import { prepareEthereumTx, serializeEthereumTx } from '../TxActions'; import * as BlockchainActions from './BlockchainActions'; import * as ValidationActions from './SendFormValidationActions'; -export type SendTxAction = { - type: typeof SEND.TX_COMPLETE, - account: Account, - selectedCurrency: string, - amount: string, - total: string, - tx: any, - nonce: number, - txid: string, - txData: any, -}; - -export type sendFormEthereumAction = { - type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE, - state: State, -} | { - type: typeof SEND.TOGGLE_ADVANCED | typeof SEND.TX_SENDING | typeof SEND.TX_ERROR, -} | SendTxAction; - // list of all actions which has influence on "sendFormEthereum" reducer // other actions will be ignored const actions = [ @@ -542,17 +522,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge const { txid } = push.payload; - dispatch({ - type: SEND.TX_COMPLETE, - account, - selectedCurrency: currentState.currency, - amount: currentState.amount, - total: currentState.total, - tx: txData, - nonce, - txid, - txData, - }); + dispatch({ type: SEND.TX_COMPLETE }); dispatch({ type: PENDING.ADD, diff --git a/src/actions/ripple/SendFormActions.js b/src/actions/ripple/SendFormActions.js index df241259..ed80e0a8 100644 --- a/src/actions/ripple/SendFormActions.js +++ b/src/actions/ripple/SendFormActions.js @@ -19,23 +19,10 @@ import type { TrezorDevice, } from 'flowtype'; import type { State } from 'reducers/SendFormRippleReducer'; -import type { Account } from 'reducers/AccountsReducer'; import * as SessionStorageActions from '../SessionStorageActions'; import * as ValidationActions from './SendFormValidationActions'; -export type SendTxAction = { - type: typeof SEND.TX_COMPLETE, - account: Account, - selectedCurrency: string, - amount: string, - total: string, - tx: any, - nonce: number, - txid: string, - txData: any, -}; - /* * Called from WalletService */ @@ -251,17 +238,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge const { txid } = push.payload; - dispatch({ - type: SEND.TX_COMPLETE, - account, - selectedCurrency: currentState.networkSymbol, - amount: currentState.amount, - total: currentState.total, - tx: {}, - nonce: account.nonce, - txid, - txData: {}, - }); + dispatch({ type: SEND.TX_COMPLETE }); // clear session storage dispatch(SessionStorageActions.clear()); diff --git a/src/services/LocalStorageService.js b/src/services/LocalStorageService.js index ffd984e0..3f9775be 100644 --- a/src/services/LocalStorageService.js +++ b/src/services/LocalStorageService.js @@ -6,7 +6,6 @@ import * as CONNECT from 'actions/constants/TrezorConnect'; import * as TOKEN from 'actions/constants/token'; import * as ACCOUNT from 'actions/constants/account'; import * as DISCOVERY from 'actions/constants/discovery'; -import * as SEND from 'actions/constants/send'; import * as PENDING from 'actions/constants/pendingTx'; import * as WALLET from 'actions/constants/wallet'; @@ -59,7 +58,7 @@ const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: Middlewar api.dispatch(LocalStorageActions.save()); break; - case SEND.TX_COMPLETE: + case PENDING.ADD: case PENDING.TX_RESOLVED: case PENDING.TX_REJECTED: api.dispatch(LocalStorageActions.save()); From ea36e56eceb31818f213d2c4f471d5688602d4c3 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 3 Dec 2018 21:59:38 +0100 Subject: [PATCH 43/72] eslint --- src/actions/TxActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/TxActions.js b/src/actions/TxActions.js index ca9d3560..db5a2542 100644 --- a/src/actions/TxActions.js +++ b/src/actions/TxActions.js @@ -12,7 +12,7 @@ import type { PromiseAction, } from 'flowtype'; -import type { EthereumTransaction, RippleTransaction } from 'trezor-connect'; +import type { EthereumTransaction } from 'trezor-connect'; import type { Token } from 'reducers/TokensReducer'; type EthereumTxRequest = { From ceaa1a5353c55446ae401a347def8e2da02b0679 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 4 Dec 2018 16:19:46 +0100 Subject: [PATCH 44/72] bump trezor-connect --- package.json | 2 +- yarn.lock | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 695f239d..73703772 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "styled-components": "^3.4.9", "styled-media-query": "^2.0.2", "styled-normalize": "^8.0.0", - "trezor-connect": "6.0.2", + "trezor-connect": "^6.0.3-beta", "web3": "1.0.0-beta.35", "webpack": "^4.16.3", "webpack-build-notifier": "^0.1.29", diff --git a/yarn.lock b/yarn.lock index 9c15578e..2af7cb41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3931,6 +3931,10 @@ events@^1.0.0, events@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" +events@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" + eventsource@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" @@ -10274,12 +10278,19 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -trezor-connect@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.2.tgz#a4ca892cc4a167b34b97644e1404a56f6a110379" +trezor-blockchain-link@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trezor-blockchain-link/-/trezor-blockchain-link-0.0.1.tgz#c272bf57838842cf1820adcd85dddf63b43f05ad" + dependencies: + events "^3.0.0" + +trezor-connect@^6.0.3-beta: + version "6.0.3-beta" + resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.3-beta.tgz#9662b0ae704b9e1a7f65419cb15cab43043f0690" dependencies: babel-runtime "^6.26.0" events "^1.1.1" + trezor-blockchain-link "^0.0.1" whatwg-fetch "^2.0.4" trim-newlines@^1.0.0: From 6bf2e9da23c806b90e508989128889f8bc7ce9ea Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 4 Dec 2018 16:37:43 +0100 Subject: [PATCH 45/72] trezor-connect@beta.2 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 73703772..5766a76c 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "styled-components": "^3.4.9", "styled-media-query": "^2.0.2", "styled-normalize": "^8.0.0", - "trezor-connect": "^6.0.3-beta", + "trezor-connect": "^6.0.3-beta.2", "web3": "1.0.0-beta.35", "webpack": "^4.16.3", "webpack-build-notifier": "^0.1.29", diff --git a/yarn.lock b/yarn.lock index 2af7cb41..6a55745c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10284,9 +10284,9 @@ trezor-blockchain-link@^0.0.1: dependencies: events "^3.0.0" -trezor-connect@^6.0.3-beta: - version "6.0.3-beta" - resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.3-beta.tgz#9662b0ae704b9e1a7f65419cb15cab43043f0690" +trezor-connect@^6.0.3-beta.2: + version "6.0.3-beta.2" + resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.3-beta.2.tgz#6b39c8b155ac3b56d5bc98a9ea5cab290be9db18" dependencies: babel-runtime "^6.26.0" events "^1.1.1" From 473401ccf74eccd619210f39d06bbe8df74794f4 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 09:44:00 +0100 Subject: [PATCH 46/72] Sendform: fix after rebase --- .../views/Account/Send/ethereum/index.js | 287 +++++++------ src/views/Wallet/views/Account/Send/index.js | 403 ------------------ .../views/Account/Send/ripple/Container.js | 2 +- .../Wallet/views/Account/Send/ripple/index.js | 159 ++++--- 4 files changed, 223 insertions(+), 628 deletions(-) diff --git a/src/views/Wallet/views/Account/Send/ethereum/index.js b/src/views/Wallet/views/Account/Send/ethereum/index.js index 97638549..52e605d6 100644 --- a/src/views/Wallet/views/Account/Send/ethereum/index.js +++ b/src/views/Wallet/views/Account/Send/ethereum/index.js @@ -10,6 +10,7 @@ import Link from 'components/Link'; import ICONS from 'config/icons'; import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables'; import colors from 'config/colors'; +import Title from 'views/Wallet/components/Title'; import P from 'components/Paragraph'; import { H2 } from 'components/Heading'; import Content from 'views/Wallet/components/Content'; @@ -34,7 +35,7 @@ const AmountInputLabel = styled.span` `; const InputRow = styled.div` - margin: 20px 0; + padding: 0 0 15px 0; `; const SetMaxAmountButton = styled(Button)` @@ -250,155 +251,153 @@ const AccountSend = (props: Props) => { return ( - -

Send Ethereum or tokens

- - onAddressChange(event.target.value)} - /> - + Send Ethereum or tokens + + onAddressChange(event.target.value)} + /> + - - - Amount - {(isCurrentCurrencyToken && selectedToken) && ( - You have: {selectedTokenBalance} {selectedToken.symbol} - )} - - )} - value={amount} - onChange={event => onAmountChange(event.target.value)} - bottomText={errors.amount || warnings.amount || infos.amount} - sideAddons={[ - ( - onSetMax()} - isActive={setMax} - > - {!setMax && ( - - )} - {setMax && ( - - )} - Set max - - ), - ( - - ), - ]} - /> - - - - - Fee - {gasPriceNeedsUpdate && ( - - - Recommended fees updated. Click here to use them - - )} - - + Amount + {(isCurrentCurrencyToken && selectedToken) && ( + You have: {selectedTokenBalance} {selectedToken.symbol} + )} + )} - + value={amount} + onChange={event => onAmountChange(event.target.value)} + bottomText={errors.amount || warnings.amount || infos.amount} + sideAddons={[ + ( + onSetMax()} + isActive={setMax} + > + {!setMax && ( + + )} + {setMax && ( + + )} + Set max + + ), + ( + + ), + ]} + /> + - {advanced && ( - - onSend()} - > - {sendButtonText} - - - )} + + + Fee + {gasPriceNeedsUpdate && ( + + + Recommended fees updated. Click here to use them + + )} + + onAddressChange(event.target.value)} - /> - - - - Amount - {(isCurrentCurrencyToken && selectedToken) && ( - You have: {selectedTokenBalance} {selectedToken.symbol} - )} - - )} - value={amount} - onChange={event => onAmountChange(event.target.value)} - bottomText={errors.amount || warnings.amount || infos.amount} - sideAddons={[ - ( - onSetMax()} - isActive={setMax} - > - {!setMax && ( - - )} - {setMax && ( - - )} - Set max - - ), - ( - - ), - ]} - /> - - - - - Fee - {gasPriceNeedsUpdate && ( - - - Recommended fees updated. Click here to use them - - )} - - onAddressChange(event.target.value)} - /> - + Send Ripple + + onAddressChange(event.target.value)} + /> + - - onAmountChange(event.target.value)} - bottomText={errors.amount || warnings.amount || infos.amount} - sideAddons={[ - ( - onSetMax()} - isActive={setMax} - > - {!setMax && ( - - )} - {setMax && ( - - )} - Set max - - ), - ( - - ), - ]} - /> - + + onAmountChange(event.target.value)} + bottomText={errors.amount || warnings.amount || infos.amount} + sideAddons={[ + ( + onSetMax()} + isActive={setMax} + > + {!setMax && ( + + )} + {setMax && ( + + )} + Set max + + ), + ( + + ), + ]} + /> + - + onSend()} > - onSend()} - > - {sendButtonText} - - + {sendButtonText} + + - {props.selectedAccount.pending.length > 0 && ( - - )} -
+ {props.selectedAccount.pending.length > 0 && ( + + )}
); }; From 076bba4ba3b4625906779e8918dacdf6d9d9e939 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 10:58:39 +0100 Subject: [PATCH 47/72] remove unused imports --- src/views/Wallet/views/Account/Send/ethereum/index.js | 1 - src/views/Wallet/views/Account/Send/ripple/index.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/views/Wallet/views/Account/Send/ethereum/index.js b/src/views/Wallet/views/Account/Send/ethereum/index.js index 52e605d6..6e445019 100644 --- a/src/views/Wallet/views/Account/Send/ethereum/index.js +++ b/src/views/Wallet/views/Account/Send/ethereum/index.js @@ -12,7 +12,6 @@ import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables'; import colors from 'config/colors'; import Title from 'views/Wallet/components/Title'; import P from 'components/Paragraph'; -import { H2 } from 'components/Heading'; import Content from 'views/Wallet/components/Content'; import type { Token } from 'flowtype'; import AdvancedForm from '../components/AdvancedForm'; diff --git a/src/views/Wallet/views/Account/Send/ripple/index.js b/src/views/Wallet/views/Account/Send/ripple/index.js index 82961712..2e14c28d 100644 --- a/src/views/Wallet/views/Account/Send/ripple/index.js +++ b/src/views/Wallet/views/Account/Send/ripple/index.js @@ -9,7 +9,6 @@ import Icon from 'components/Icon'; import ICONS from 'config/icons'; import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables'; import colors from 'config/colors'; -import { H2 } from 'components/Heading'; import Title from 'views/Wallet/components/Title'; import Content from 'views/Wallet/components/Content'; import PendingTransactions from '../components/PendingTransactions'; From ebce4562df8274b524fa001eeeca6c704fe018ee Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 10:58:54 +0100 Subject: [PATCH 48/72] add Tittle to receive component --- .../views/Account/Receive/ethereum/index.js | 114 +++++++++--------- .../views/Account/Receive/ripple/index.js | 114 +++++++++--------- 2 files changed, 112 insertions(+), 116 deletions(-) diff --git a/src/views/Wallet/views/Account/Receive/ethereum/index.js b/src/views/Wallet/views/Account/Receive/ethereum/index.js index b0666921..d6e976c5 100644 --- a/src/views/Wallet/views/Account/Receive/ethereum/index.js +++ b/src/views/Wallet/views/Account/Receive/ethereum/index.js @@ -4,7 +4,7 @@ import { QRCode } from 'react-qr-svg'; import styled from 'styled-components'; import media from 'styled-media-query'; -import { H2 } from 'components/Heading'; +import Title from 'views/Wallet/components/Title'; import Button from 'components/Button'; import Icon from 'components/Icon'; import Tooltip from 'components/Tooltip'; @@ -113,66 +113,64 @@ const AccountReceive = (props: Props) => { return ( - -

Receive Ethereum or tokens

- - - - - Receive Ethereum or tokens + + + + + + Check address on your Trezor +
+ ) : null} + icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( + - Check address on your Trezor - - ) : null} - icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( - - )} - > - props.showAddress(account.addressPath)}> - - - - )} - /> - {!(addressVerified || addressUnverified) && ( - props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> - Show full address - + )} + > + props.showAddress(account.addressPath)}> + + + )} - - {(addressVerified || addressUnverified) && !isAddressVerifying && ( - - - - + /> + {!(addressVerified || addressUnverified) && ( + props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> + Show full address + )} - - + + {(addressVerified || addressUnverified) && !isAddressVerifying && ( + + + + + )} +
); }; diff --git a/src/views/Wallet/views/Account/Receive/ripple/index.js b/src/views/Wallet/views/Account/Receive/ripple/index.js index db7974b6..723634b9 100644 --- a/src/views/Wallet/views/Account/Receive/ripple/index.js +++ b/src/views/Wallet/views/Account/Receive/ripple/index.js @@ -4,7 +4,7 @@ import { QRCode } from 'react-qr-svg'; import styled from 'styled-components'; import media from 'styled-media-query'; -import { H2 } from 'components/Heading'; +import Title from 'views/Wallet/components/Title'; import Button from 'components/Button'; import Icon from 'components/Icon'; import Tooltip from 'components/Tooltip'; @@ -113,66 +113,64 @@ const AccountReceive = (props: Props) => { return ( - -

Receive Ripple

- - - - - Receive Ripple + + + + + + Check address on your Trezor +
+ ) : null} + icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( + - Check address on your Trezor - - ) : null} - icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( - - )} - > - props.showAddress(account.addressPath)}> - - - - )} - /> - {!(addressVerified || addressUnverified) && ( - props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> - Show full address - + )} + > + props.showAddress(account.addressPath)}> + + + )} - - {(addressVerified || addressUnverified) && !isAddressVerifying && ( - - - - + /> + {!(addressVerified || addressUnverified) && ( + props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> + Show full address + )} - - + + {(addressVerified || addressUnverified) && !isAddressVerifying && ( + + + + + )} +
); }; From 38bc1c7c303414f60d554ecc9df4d8a72e2309ac Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 10:59:12 +0100 Subject: [PATCH 49/72] nitpick: ampersand --- src/views/Wallet/views/Account/SignVerify/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/Wallet/views/Account/SignVerify/index.js b/src/views/Wallet/views/Account/SignVerify/index.js index fad97374..0d310f06 100644 --- a/src/views/Wallet/views/Account/SignVerify/index.js +++ b/src/views/Wallet/views/Account/SignVerify/index.js @@ -76,7 +76,7 @@ class SignVerify extends Component { const verifyAddressError = this.getError('verifyAddress'); return ( - Sign & Verify + Sign & Verify From 6d78e3a8f74770f25e34d7e52baf78944f93047b Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 11:46:07 +0100 Subject: [PATCH 50/72] remove conflicts from yarn.lock --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6a55745c..68475d66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8504,15 +8504,15 @@ react-select@2.0.0: react-input-autosize "^2.2.1" react-transition-group "^2.2.1" -<<<<<<< HEAD -react-textarea-autosize@^7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.0.4.tgz#4e4be649b544a88713e7b5043f76950f35d3d503" -======= react-textarea-autosize@^6.1.0: version "6.1.0" resolved "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5" ->>>>>>> master + dependencies: + prop-types "^15.6.0" + +react-textarea-autosize@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.0.4.tgz#4e4be649b544a88713e7b5043f76950f35d3d503" dependencies: prop-types "^15.6.0" From 3ae45ddd7e4feff790d5db4b2e83593b564d629a Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 14:11:03 +0100 Subject: [PATCH 51/72] update fee in reducer only if changed --- src/actions/BlockchainActions.js | 1 + src/actions/ripple/BlockchainActions.js | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 5393813e..9dde3442 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -16,6 +16,7 @@ export type BlockchainAction = { type: typeof BLOCKCHAIN.READY, } | { type: typeof BLOCKCHAIN.UPDATE_FEE, + shortcut: string, fee: string, } diff --git a/src/actions/ripple/BlockchainActions.js b/src/actions/ripple/BlockchainActions.js index 14f7e020..3f4cf884 100644 --- a/src/actions/ripple/BlockchainActions.js +++ b/src/actions/ripple/BlockchainActions.js @@ -23,16 +23,22 @@ export const subscribe = (network: string): PromiseAction => async (dispat }; -export const onBlockMined = (network: string): PromiseAction => async (dispatch: Dispatch): Promise => { +export const onBlockMined = (network: string): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { const fee = await TrezorConnect.blockchainGetFee({ coin: network, }); if (!fee.success) return; - dispatch({ - type: BLOCKCHAIN.UPDATE_FEE, - fee: fee.payload, - }); + const blockchain = getState().blockchain.find(b => b.shortcut === network); + if (!blockchain) return; + + if (fee.payload !== blockchain.fee) { + dispatch({ + type: BLOCKCHAIN.UPDATE_FEE, + shortcut: network, + fee: fee.payload, + }); + } }; export const onNotification = (payload: $ElementType): PromiseAction => async (dispatch: Dispatch, getState: GetState): Promise => { From 4492dbae63f8301a9575bbd5040a8d6ed0c741e9 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 14:12:23 +0100 Subject: [PATCH 52/72] feeLevels in ripple --- src/actions/ripple/SendFormActions.js | 4 +- .../ripple/SendFormValidationActions.js | 75 +++++++++++++++++-- src/reducers/SendFormRippleReducer.js | 4 + 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/actions/ripple/SendFormActions.js b/src/actions/ripple/SendFormActions.js index ed80e0a8..17e9928d 100644 --- a/src/actions/ripple/SendFormActions.js +++ b/src/actions/ripple/SendFormActions.js @@ -45,7 +45,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction = // handle gasPrice update from backend // recalculate fee levels if needed if (action.type === BLOCKCHAIN.UPDATE_FEE) { - // dispatch(ValidationActions.onGasPriceUpdated(action.network, action.gasPrice)); + dispatch(ValidationActions.onFeeUpdated(action.shortcut, action.fee)); return; } @@ -94,7 +94,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS return; } - const feeLevels = ValidationActions.getFeeLevels(network.symbol); + const feeLevels = dispatch(ValidationActions.getFeeLevels(network.symbol)); const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel); dispatch({ diff --git a/src/actions/ripple/SendFormValidationActions.js b/src/actions/ripple/SendFormValidationActions.js index 2b4f194d..c573997a 100644 --- a/src/actions/ripple/SendFormValidationActions.js +++ b/src/actions/ripple/SendFormValidationActions.js @@ -1,6 +1,7 @@ /* @flow */ import BigNumber from 'bignumber.js'; +import * as SEND from 'actions/constants/send'; import { findDevice, getPendingAmount } from 'reducers/utils'; import { toDecimalAmount } from 'utils/formatUtils'; @@ -16,6 +17,47 @@ const XRP_ADDRESS_RE = new RegExp('^r[1-9A-HJ-NP-Za-km-z]{25,34}$'); const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$'); const XRP_6_RE = new RegExp('^(0|0\\.([0-9]{0,6})?|[1-9][0-9]*\\.?([0-9]{0,6})?|\\.[0-9]{0,6})$'); +/* +* Called from SendFormActions.observe +* Reaction for BLOCKCHAIN.FEE_UPDATED action +*/ +export const onFeeUpdated = (network: string, fee: string): PayloadAction => (dispatch: Dispatch, getState: GetState): void => { + const state = getState().sendFormRipple; + if (network === state.networkSymbol) return; + + + if (!state.untouched) { + // if there is a transaction draft let the user know + // and let him update manually + dispatch({ + type: SEND.CHANGE, + networkType: 'ripple', + state: { + ...state, + feeNeedsUpdate: true, + recommendedFee: fee, + }, + }); + return; + } + + // automatically update feeLevels and gasPrice + const feeLevels = dispatch(getFeeLevels(state.networkSymbol)); + const selectedFeeLevel = getSelectedFeeLevel(feeLevels, state.selectedFeeLevel); + dispatch({ + type: SEND.CHANGE, + networkType: 'ripple', + state: { + ...state, + feeNeedsUpdate: false, + recommendedFee: fee, + gasPrice: selectedFeeLevel.gasPrice, + feeLevels, + selectedFeeLevel, + }, + }); +}; + /* * Recalculate amount, total and fees */ @@ -170,13 +212,34 @@ const calculateMaxAmount = (balance: BigNumber, fee: string): string => { } }; -export const getFeeLevels = (shortcut: string): Array => ([ - { +export const getFeeLevels = (symbol: string): PayloadAction> => (dispatch: Dispatch, getState: GetState): Array => { + const blockchain = getState().blockchain.find(b => b.shortcut === symbol.toLowerCase()); + if (!blockchain) { + // return default fee levels (TODO: get them from config) + return [{ + value: 'Normal', + gasPrice: '0.000012', + label: `0.000012 ${symbol}`, + }]; + } + + const xrpDrops = toDecimalAmount(blockchain.fee, 6); + + // TODO: calc fee levels + return [{ value: 'Normal', - gasPrice: '1', - label: `1 ${shortcut}`, - }, -]); + gasPrice: xrpDrops, + label: `${xrpDrops} ${symbol}`, + }]; +}; + +// export const getFeeLevels = (shortcut: string): Array => ([ +// { +// value: 'Normal', +// gasPrice: '1', +// label: `1 ${shortcut}`, +// }, +// ]); export const getSelectedFeeLevel = (feeLevels: Array, selected: FeeLevel): FeeLevel => { diff --git a/src/reducers/SendFormRippleReducer.js b/src/reducers/SendFormRippleReducer.js index 90af5cac..f340936c 100644 --- a/src/reducers/SendFormRippleReducer.js +++ b/src/reducers/SendFormRippleReducer.js @@ -24,6 +24,8 @@ export type State = { setMax: boolean; feeLevels: Array; selectedFeeLevel: FeeLevel; + recommendedFee: string; + feeNeedsUpdate: boolean; sequence: string; total: string; @@ -51,6 +53,8 @@ export const initialState: State = { gasPrice: '0', value: 'Normal', }, + recommendedFee: '0', + feeNeedsUpdate: false, sequence: '0', total: '0', From e130b1edc0c9d1e022cb666367b80446c5d00e00 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 14:12:53 +0100 Subject: [PATCH 53/72] update recommended fee in blockchain reducer --- src/reducers/BlockchainReducer.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/reducers/BlockchainReducer.js b/src/reducers/BlockchainReducer.js index 48a44db2..e92d9468 100644 --- a/src/reducers/BlockchainReducer.js +++ b/src/reducers/BlockchainReducer.js @@ -1,6 +1,7 @@ /* @flow */ -import { BLOCKCHAIN } from 'trezor-connect'; +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'; @@ -66,15 +67,28 @@ const onBlock = (state: State, action: BlockchainBlock): State => { return state; }; +const updateFee = (state: State, shortcut: string, fee: string): State => { + const network = state.find(b => b.shortcut === shortcut); + if (!network) return state; + + const others = state.filter(b => b !== network); + return others.concat([{ + ...network, + fee, + }]); +}; + export default (state: State = initialState, action: Action): State => { switch (action.type) { - case BLOCKCHAIN.CONNECT: + case BLOCKCHAIN_EVENT.CONNECT: return onConnect(state, action); - case BLOCKCHAIN.ERROR: + case BLOCKCHAIN_EVENT.ERROR: return onError(state, action); - case BLOCKCHAIN.BLOCK: + case BLOCKCHAIN_EVENT.BLOCK: return onBlock(state, action); + case BLOCKCHAIN_ACTION.UPDATE_FEE: + return updateFee(state, action.shortcut, action.fee); default: return state; From d7e0fb91cdf981a453241b858225563bbaee41f9 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 14:40:28 +0100 Subject: [PATCH 54/72] add SignTx modal with SendFormRippleReducer --- src/components/modals/Container.js | 6 ++++-- src/components/modals/confirm/SignTx/index.js | 8 ++++---- src/components/modals/index.js | 7 +++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/modals/Container.js b/src/components/modals/Container.js index 3a80f075..abe0bf33 100644 --- a/src/components/modals/Container.js +++ b/src/components/modals/Container.js @@ -19,7 +19,8 @@ type StateProps = { devices: $ElementType, connect: $ElementType, selectedAccount: $ElementType, - sendForm: $ElementType, + sendFormEthereum: $ElementType, + sendFormRipple: $ElementType, receive: $ElementType, localStorage: $ElementType, wallet: $ElementType, @@ -38,7 +39,8 @@ const mapStateToProps: MapStateToProps = (state: St devices: state.devices, connect: state.connect, selectedAccount: state.selectedAccount, - sendForm: state.sendFormEthereum, + sendFormEthereum: state.sendFormEthereum, + sendFormRipple: state.sendFormRipple, receive: state.receive, localStorage: state.localStorage, wallet: state.wallet, diff --git a/src/components/modals/confirm/SignTx/index.js b/src/components/modals/confirm/SignTx/index.js index 4668979f..1ae4297b 100644 --- a/src/components/modals/confirm/SignTx/index.js +++ b/src/components/modals/confirm/SignTx/index.js @@ -12,12 +12,11 @@ import P from 'components/Paragraph'; import Icon from 'components/Icon'; import { H3 } from 'components/Heading'; -import type { TrezorDevice } from 'flowtype'; -import type { Props as BaseProps } from '../../Container'; +import type { TrezorDevice, State } from 'flowtype'; type Props = { device: TrezorDevice; - sendForm: $ElementType; + sendForm: $ElementType | $ElementType; } const Wrapper = styled.div` @@ -51,10 +50,11 @@ const ConfirmSignTx = (props: Props) => { const { amount, address, - currency, selectedFeeLevel, } = props.sendForm; + const currency: string = typeof props.sendForm.currency === 'string' ? props.sendForm.currency : props.sendForm.networkSymbol; + return (
diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 9e0a6330..e73bfdbf 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -88,13 +88,16 @@ const getDeviceContextModal = (props: Props) => { case 'ButtonRequest_PassphraseType': return ; - case 'ButtonRequest_SignTx': - return ; + case 'ButtonRequest_SignTx': { + const sendForm = props.selectedAccount.network && props.selectedAccount.network.type === 'ethereum' ? props.sendFormEthereum : props.sendFormRipple; + return ; + } case 'ButtonRequest_ProtectCall': return ; case 'ButtonRequest_Other': + case 'ButtonRequest_ConfirmOutput': return ; case RECEIVE.REQUEST_UNVERIFIED: From f4985c0b7ccfce7e7f9734e24c47a09b9b960f08 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 5 Dec 2018 15:10:38 +0100 Subject: [PATCH 55/72] modals container eslint fix --- src/components/modals/Container.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/modals/Container.js b/src/components/modals/Container.js index abe0bf33..fa95094e 100644 --- a/src/components/modals/Container.js +++ b/src/components/modals/Container.js @@ -11,7 +11,7 @@ import type { State, Dispatch } from 'flowtype'; import Modal from './index'; -type OwnProps = { } +type OwnProps = {}; type StateProps = { modal: $ElementType, @@ -24,12 +24,12 @@ type StateProps = { receive: $ElementType, localStorage: $ElementType, wallet: $ElementType, -} +}; type DispatchProps = { modalActions: typeof ModalActions, receiveActions: typeof ReceiveActions, -} +}; export type Props = StateProps & DispatchProps; From 85ec63b7793a43f214a56687c48dc649666688f4 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 13:25:00 +0100 Subject: [PATCH 56/72] bum trezor-connect version --- package.json | 2 +- yarn.lock | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 5766a76c..d17afa9a 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "styled-components": "^3.4.9", "styled-media-query": "^2.0.2", "styled-normalize": "^8.0.0", - "trezor-connect": "^6.0.3-beta.2", + "trezor-connect": "6.0.3-beta.4", "web3": "1.0.0-beta.35", "webpack": "^4.16.3", "webpack-build-notifier": "^0.1.29", diff --git a/yarn.lock b/yarn.lock index 68475d66..75aed854 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3931,10 +3931,6 @@ events@^1.0.0, events@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" -events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - eventsource@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" @@ -10278,19 +10274,12 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -trezor-blockchain-link@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trezor-blockchain-link/-/trezor-blockchain-link-0.0.1.tgz#c272bf57838842cf1820adcd85dddf63b43f05ad" - dependencies: - events "^3.0.0" - -trezor-connect@^6.0.3-beta.2: - version "6.0.3-beta.2" - resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.3-beta.2.tgz#6b39c8b155ac3b56d5bc98a9ea5cab290be9db18" +trezor-connect@6.0.3-beta.4: + version "6.0.3-beta.4" + resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.3-beta.4.tgz#c0b3dfe809756b455e5b5b34fb2a104846da4f72" dependencies: babel-runtime "^6.26.0" events "^1.1.1" - trezor-blockchain-link "^0.0.1" whatwg-fetch "^2.0.4" trim-newlines@^1.0.0: From fc4ff5d71cb28adc4e35db676fc5886c07ad27ca Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 13:25:38 +0100 Subject: [PATCH 57/72] add "testnet" field to config, and filter those values for "dev build" only --- public/data/appConfig.json | 2 ++ src/actions/LocalStorageActions.js | 5 ++--- src/reducers/LocalStorageReducer.js | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public/data/appConfig.json b/public/data/appConfig.json index b484b97d..3e29f974 100644 --- a/public/data/appConfig.json +++ b/public/data/appConfig.json @@ -3,6 +3,7 @@ { "type": "ripple", "name": "Ripple Testnet", + "testnet": true, "symbol": "XRP", "shortcut": "xrp", "bip44": "m/44'/144'/a'/0/0", @@ -52,6 +53,7 @@ { "type": "ethereum", "name": "Ethereum Ropsten", + "testnet": true, "symbol": "tROP", "shortcut": "trop", "chainId": 3, diff --git a/src/actions/LocalStorageActions.js b/src/actions/LocalStorageActions.js index 16077553..b95e4ba4 100644 --- a/src/actions/LocalStorageActions.js +++ b/src/actions/LocalStorageActions.js @@ -141,10 +141,9 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise => try { const config: Config = await httpRequest(AppConfigJSON, 'json'); - // remove ropsten testnet from config networks + // remove testnets from config networks if (!buildUtils.isDev()) { - const index = config.networks.findIndex(c => c.shortcut === 'trop'); - delete config.networks[index]; + config.networks = config.networks.filter(n => !n.testnet); } const ERC20Abi = await httpRequest(Erc20AbiJSON, 'json'); diff --git a/src/reducers/LocalStorageReducer.js b/src/reducers/LocalStorageReducer.js index cb486f99..976de717 100644 --- a/src/reducers/LocalStorageReducer.js +++ b/src/reducers/LocalStorageReducer.js @@ -8,6 +8,7 @@ import type { Action } from 'flowtype'; export type Network = { type: string; name: string; + testnet?: boolean; shortcut: string; symbol: string; bip44: string; From 7fc60c5fe3b38280055fdea62314b5e845379333 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 13:25:48 +0100 Subject: [PATCH 58/72] added CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e529b40a..7175fa1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## feature/ripple +__changed__ +- Split code to coin specific components. actions and reducers +- Use TrezorConnect to communicate with trezor-blockchain-link + +__added__ +- Ripple support + ## 1.0.2-beta __changed__ - Fiat rates from coingecko (https://github.com/trezor/trezor-wallet/pull/242) From 6d3722c493bb93dc254183f0b68918e30b755f11 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 14:02:55 +0100 Subject: [PATCH 59/72] change ripple explorer url --- public/data/appConfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/data/appConfig.json b/public/data/appConfig.json index 3e29f974..2eaed83e 100644 --- a/public/data/appConfig.json +++ b/public/data/appConfig.json @@ -8,8 +8,8 @@ "shortcut": "xrp", "bip44": "m/44'/144'/a'/0/0", "explorer": { - "tx": "https://etherscan.io/tx/", - "address": "https://etherscan.io/address/" + "tx": "https://sisyfos.trezor.io/ripple-testnet-explorer/tx/", + "address": "https://sisyfos.trezor.io/ripple-testnet-explorer/address/" } }, { From 128d2380377b66ad5418b76993a2fead751f84f9 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 15:08:02 +0100 Subject: [PATCH 60/72] change "if" to "switch" --- src/actions/BlockchainActions.js | 50 ++++++++++++++++++++++---------- src/actions/DiscoveryActions.js | 30 +++++++++++-------- src/actions/ReceiveActions.js | 16 ++++++---- src/components/modals/index.js | 10 +++++-- 4 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 9dde3442..e0efb270 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -51,10 +51,14 @@ export const subscribe = (networkName: string): PromiseAction => async (di const network = config.networks.find(c => c.shortcut === networkName); if (!network) return; - if (network.type === 'ethereum') { - await dispatch(EthereumBlockchainActions.subscribe(networkName)); - } else if (network.type === 'ripple') { - await dispatch(RippleBlockchainActions.subscribe(networkName)); + switch (network.type) { + case 'ethereum': + await dispatch(EthereumBlockchainActions.subscribe(networkName)); + break; + case 'ripple': + await dispatch(RippleBlockchainActions.subscribe(networkName)); + break; + default: break; } }; @@ -66,10 +70,14 @@ export const onBlockMined = (payload: $ElementType): const network = config.networks.find(c => c.shortcut === shortcut); if (!network) return; - if (network.type === 'ethereum') { - await dispatch(EthereumBlockchainActions.onBlockMined(shortcut)); - } else if (network.type === 'ripple') { - await dispatch(RippleBlockchainActions.onBlockMined(shortcut)); + switch (network.type) { + case 'ethereum': + await dispatch(EthereumBlockchainActions.onBlockMined(shortcut)); + break; + case 'ripple': + await dispatch(RippleBlockchainActions.onBlockMined(shortcut)); + break; + default: break; } }; @@ -79,10 +87,15 @@ export const onNotification = (payload: $ElementType c.shortcut === shortcut); if (!network) return; - if (network.type === 'ethereum') { - await dispatch(EthereumBlockchainActions.onNotification()); - } else if (network.type === 'ripple') { - await dispatch(RippleBlockchainActions.onNotification(payload)); + switch (network.type) { + case 'ethereum': + // this is not working until blockchain-link will start support blockbook backends + await dispatch(EthereumBlockchainActions.onNotification()); + break; + case 'ripple': + await dispatch(RippleBlockchainActions.onNotification(payload)); + break; + default: break; } }; @@ -94,9 +107,14 @@ export const onError = (payload: $ElementType): Prom const network = config.networks.find(c => c.shortcut === shortcut); if (!network) return; - if (network.type === 'ethereum') { - await dispatch(EthereumBlockchainActions.onError(shortcut)); - } else if (network.type === 'ripple') { - // await dispatch(RippleBlockchainActions.onError(shortcut)); + switch (network.type) { + case 'ethereum': + await dispatch(EthereumBlockchainActions.onError(shortcut)); + break; + case 'ripple': + // this error is handled in BlockchainReducer + // await dispatch(RippleBlockchainActions.onBlockMined(shortcut)); + break; + default: break; } }; \ No newline at end of file diff --git a/src/actions/DiscoveryActions.js b/src/actions/DiscoveryActions.js index fc1636a2..be0ddb03 100644 --- a/src/actions/DiscoveryActions.js +++ b/src/actions/DiscoveryActions.js @@ -131,12 +131,15 @@ const begin = (device: TrezorDevice, networkName: string): AsyncAction => async let startAction: DiscoveryStartAction; try { - if (network.type === 'ethereum') { - startAction = await dispatch(EthereumDiscoveryActions.begin(device, network)); - } else if (network.type === 'ripple') { - startAction = await dispatch(RippleDiscoveryActions.begin(device, network)); - } else { - throw new Error(`DiscoveryActions.begin: Unknown network type: ${network.type}`); + switch (network.type) { + case 'ethereum': + startAction = await dispatch(EthereumDiscoveryActions.begin(device, network)); + break; + case 'ripple': + startAction = await dispatch(RippleDiscoveryActions.begin(device, network)); + break; + default: + throw new Error(`DiscoveryActions.begin: Unknown network type: ${network.type}`); } } catch (error) { dispatch({ @@ -180,12 +183,15 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy const { completed, accountIndex } = discoveryProcess; let account: Account; try { - if (network.type === 'ethereum') { - account = await dispatch(EthereumDiscoveryActions.discoverAccount(device, discoveryProcess)); - } else if (network.type === 'ripple') { - account = await dispatch(RippleDiscoveryActions.discoverAccount(device, discoveryProcess)); - } else { - throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`); + switch (network.type) { + case 'ethereum': + account = await dispatch(EthereumDiscoveryActions.discoverAccount(device, discoveryProcess)); + break; + case 'ripple': + account = await dispatch(RippleDiscoveryActions.discoverAccount(device, discoveryProcess)); + break; + default: + throw new Error(`DiscoveryActions.discoverAccount: Unknown network type: ${network.type}`); } } catch (error) { // handle not supported firmware error diff --git a/src/actions/ReceiveActions.js b/src/actions/ReceiveActions.js index a039db9e..116a5c15 100644 --- a/src/actions/ReceiveActions.js +++ b/src/actions/ReceiveActions.js @@ -73,13 +73,19 @@ export const showAddress = (path: Array): AsyncAction => async (dispatch }; let response; - if (network.type === 'ethereum') { - response = await TrezorConnect.ethereumGetAddress(params); - } else { - response = await TrezorConnect.rippleGetAddress(params); + switch (network.type) { + case 'ethereum': + response = await TrezorConnect.ethereumGetAddress(params); + break; + case 'ripple': + response = await TrezorConnect.rippleGetAddress(params); + break; + default: + response = { payload: { error: `ReceiveActions.showAddress: Unknown network type: ${network.type}` } }; + break; } - if (response && response.success) { + if (response.success) { dispatch({ type: RECEIVE.SHOW_ADDRESS, }); diff --git a/src/components/modals/index.js b/src/components/modals/index.js index e73bfdbf..1b75af8f 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -89,8 +89,14 @@ const getDeviceContextModal = (props: Props) => { return ; case 'ButtonRequest_SignTx': { - const sendForm = props.selectedAccount.network && props.selectedAccount.network.type === 'ethereum' ? props.sendFormEthereum : props.sendFormRipple; - return ; + if (!props.selectedAccount.network) return null; + switch (props.selectedAccount.network.type) { + case 'ethereum': + return ; + case 'ripple': + return ; + default: return null; + } } case 'ButtonRequest_ProtectCall': From b7790c0351f33b8d165d1a0a290dcea6f1a7f17e Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 15:08:26 +0100 Subject: [PATCH 61/72] remove comments --- src/actions/TxActions.js | 25 +---------------------- src/actions/ethereum/BlockchainActions.js | 2 -- src/actions/ripple/DiscoveryActions.js | 1 - src/actions/ripple/SendFormActions.js | 3 +-- 4 files changed, 2 insertions(+), 29 deletions(-) diff --git a/src/actions/TxActions.js b/src/actions/TxActions.js index db5a2542..e3d35cd5 100644 --- a/src/actions/TxActions.js +++ b/src/actions/TxActions.js @@ -60,27 +60,4 @@ export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction => async (): Promise => { const ethTx = new EthereumjsTx(tx); return `0x${ethTx.serialize().toString('hex')}`; -}; - -// type RippleTxRequest = { -// network: string; -// from: string; -// to: string; -// amount: string; -// data: string; -// gasLimit: string; -// gasPrice: string; -// nonce: number; -// } - -// export const prepareRippleTx = (tx: RippleTxRequest): PromiseAction => async (dispatch: Dispatch): Promise => { -// // TODO: fetch account sequence -// return { -// fee: string, -// flags?: number, -// sequence?: number, -// maxLedgerVersion?: number, // Proto: "last_ledger_sequence" -// payment: Payment, -// } - -// }; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/actions/ethereum/BlockchainActions.js b/src/actions/ethereum/BlockchainActions.js index 355ffda2..fc27ce05 100644 --- a/src/actions/ethereum/BlockchainActions.js +++ b/src/actions/ethereum/BlockchainActions.js @@ -17,7 +17,6 @@ import * as AccountsActions from 'actions/AccountsActions'; export const discoverAccount = (device: TrezorDevice, address: string, network: string): PromiseAction => async (dispatch: Dispatch): Promise => { // get data from connect - // Temporary disabled, enable after trezor-connect@5.0.32 release const txs = await TrezorConnect.ethereumGetAccountInfo({ account: { address, @@ -35,7 +34,6 @@ export const discoverAccount = (device: TrezorDevice, address: string, network: // blockbook web3 fallback const web3account = await dispatch(Web3Actions.discoverAccount(address, network)); - // return { transactions: txs.payload, ...web3account }; return { address, transactions: txs.payload.transactions, diff --git a/src/actions/ripple/DiscoveryActions.js b/src/actions/ripple/DiscoveryActions.js index c4c82b26..b87c7310 100644 --- a/src/actions/ripple/DiscoveryActions.js +++ b/src/actions/ripple/DiscoveryActions.js @@ -36,7 +36,6 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover const { accountIndex } = discoveryProcess; const path = network.bip44.slice(0).replace('a', accountIndex.toString()); - // $FlowIssue npm not released yet const response = await TrezorConnect.rippleGetAccountInfo({ device: { path: device.path, diff --git a/src/actions/ripple/SendFormActions.js b/src/actions/ripple/SendFormActions.js index 17e9928d..c4ae7304 100644 --- a/src/actions/ripple/SendFormActions.js +++ b/src/actions/ripple/SendFormActions.js @@ -72,7 +72,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction = /* * Called from "observe" action -* Initialize "sendForm" reducer data +* Initialize "sendFormRipple" reducer data * Get data either from session storage or "selectedAccount" reducer */ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { @@ -85,7 +85,6 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS const stateFromStorage = dispatch(SessionStorageActions.loadRippleDraftTransaction()); if (stateFromStorage) { - // TODO: consider if current gasPrice should be set here as "recommendedGasPrice" dispatch({ type: SEND.INIT, networkType: 'ripple', From 1ac33b05ee8157fa18186ab8f301e3100f8a0b24 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 15:08:38 +0100 Subject: [PATCH 62/72] update webpack.local config --- webpack/local.babel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/local.babel.js b/webpack/local.babel.js index e730ec46..b6d1894e 100644 --- a/webpack/local.babel.js +++ b/webpack/local.babel.js @@ -50,7 +50,7 @@ module.exports = { rules: [ { test: /\.js?$/, - exclude: [/node_modules/, /blockchain-link\/build\/workers/], + exclude: [/node_modules/, /trezor-blockchain-link\/build\/workers/], use: ['babel-loader'], }, { From 15ad3ceee745b40ea8bcf6fa39da46a99240a51a Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Tue, 11 Dec 2018 15:28:44 +0100 Subject: [PATCH 63/72] css changes in views/Account components from master --- .../views/Account/Receive/ethereum/index.js | 133 +++++++++--------- .../views/Account/Receive/ripple/index.js | 133 +++++++++--------- .../views/Account/Send/ethereum/index.js | 13 +- .../Wallet/views/Account/Send/ripple/index.js | 5 +- .../views/Account/Summary/ethereum/index.js | 3 +- .../views/Account/Summary/ripple/index.js | 3 +- 6 files changed, 150 insertions(+), 140 deletions(-) diff --git a/src/views/Wallet/views/Account/Receive/ethereum/index.js b/src/views/Wallet/views/Account/Receive/ethereum/index.js index d6e976c5..291fbcc8 100644 --- a/src/views/Wallet/views/Account/Receive/ethereum/index.js +++ b/src/views/Wallet/views/Account/Receive/ethereum/index.js @@ -2,7 +2,6 @@ import React from 'react'; import { QRCode } from 'react-qr-svg'; import styled from 'styled-components'; -import media from 'styled-media-query'; import Title from 'views/Wallet/components/Title'; import Button from 'components/Button'; @@ -20,7 +19,7 @@ import VerifyAddressTooltip from '../components/VerifyAddressTooltip'; import type { Props } from './Container'; const Label = styled.div` - padding: 25px 0 5px 0; + padding-bottom: 10px; color: ${colors.TEXT_SECONDARY}; `; @@ -43,14 +42,17 @@ const ShowAddressButton = styled(Button)` display: flex; height: 40px; align-items: center; + align-self: flex-end; justify-content: center; border-top-left-radius: 0; border-bottom-left-radius: 0; - ${media.lessThan('795px')` + @media screen and (max-width: 795px) { margin-top: 10px; - `} + align-self: auto; + border-radius: 3px; + } `; const ShowAddressIcon = styled(Icon)` @@ -63,23 +65,24 @@ const EyeButton = styled(Button)` z-index: 10001; padding: 0; width: 30px; - background: white; + background: transparent; top: 5px; position: absolute; right: 10px; &:hover { - background: white; + background: transparent; } `; const Row = styled.div` display: flex; width: 100%; + padding-bottom: 28px; - ${media.lessThan('795px')` + @media screen and (max-width: 795px) { flex-direction: column; - `} + } `; const QrWrapper = styled.div` @@ -113,66 +116,68 @@ const AccountReceive = (props: Props) => { return ( - Receive Ethereum or tokens - - - - - - Check address on your Trezor - - ) : null} - icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( - - )} - > - props.showAddress(account.addressPath)}> + + Receive Ethereum or tokens + + + - - - )} - /> - {!(addressVerified || addressUnverified) && ( - props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> - Show full address - - )} - - {(addressVerified || addressUnverified) && !isAddressVerifying && ( - - - + ) : null} + icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( + + )} + > + props.showAddress(account.addressPath)}> + + + + )} /> - - )} - + {!(addressVerified || addressUnverified) && ( + props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> + Show full address + + )} + + {(addressVerified || addressUnverified) && !isAddressVerifying && ( + + + + + )} + + ); }; -export default AccountReceive; +export default AccountReceive; \ No newline at end of file diff --git a/src/views/Wallet/views/Account/Receive/ripple/index.js b/src/views/Wallet/views/Account/Receive/ripple/index.js index 723634b9..6bc0d7ed 100644 --- a/src/views/Wallet/views/Account/Receive/ripple/index.js +++ b/src/views/Wallet/views/Account/Receive/ripple/index.js @@ -2,7 +2,6 @@ import React from 'react'; import { QRCode } from 'react-qr-svg'; import styled from 'styled-components'; -import media from 'styled-media-query'; import Title from 'views/Wallet/components/Title'; import Button from 'components/Button'; @@ -20,7 +19,7 @@ import VerifyAddressTooltip from '../components/VerifyAddressTooltip'; import type { Props } from './Container'; const Label = styled.div` - padding: 25px 0 5px 0; + padding-bottom: 10px; color: ${colors.TEXT_SECONDARY}; `; @@ -43,14 +42,17 @@ const ShowAddressButton = styled(Button)` display: flex; height: 40px; align-items: center; + align-self: flex-end; justify-content: center; border-top-left-radius: 0; border-bottom-left-radius: 0; - ${media.lessThan('795px')` + @media screen and (max-width: 795px) { margin-top: 10px; - `} + align-self: auto; + border-radius: 3px; + } `; const ShowAddressIcon = styled(Icon)` @@ -63,23 +65,24 @@ const EyeButton = styled(Button)` z-index: 10001; padding: 0; width: 30px; - background: white; + background: transparent; top: 5px; position: absolute; right: 10px; &:hover { - background: white; + background: transparent; } `; const Row = styled.div` display: flex; width: 100%; + padding-bottom: 28px; - ${media.lessThan('795px')` + @media screen and (max-width: 795px) { flex-direction: column; - `} + } `; const QrWrapper = styled.div` @@ -113,66 +116,68 @@ const AccountReceive = (props: Props) => { return ( - Receive Ripple - - - - - - Check address on your Trezor - - ) : null} - icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( - - )} - > - props.showAddress(account.addressPath)}> + + Receive Ripple + + + - - - )} - /> - {!(addressVerified || addressUnverified) && ( - props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> - Show full address - - )} - - {(addressVerified || addressUnverified) && !isAddressVerifying && ( - - - + ) : null} + icon={((addressVerified || addressUnverified) && !isAddressVerifying) && ( + + )} + > + props.showAddress(account.addressPath)}> + + + + )} /> - - )} - + {!(addressVerified || addressUnverified) && ( + props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}> + Show full address + + )} + + {(addressVerified || addressUnverified) && !isAddressVerifying && ( + + + + + )} + + ); }; -export default AccountReceive; +export default AccountReceive; \ No newline at end of file diff --git a/src/views/Wallet/views/Account/Send/ethereum/index.js b/src/views/Wallet/views/Account/Send/ethereum/index.js index 6e445019..4a97f416 100644 --- a/src/views/Wallet/views/Account/Send/ethereum/index.js +++ b/src/views/Wallet/views/Account/Send/ethereum/index.js @@ -34,7 +34,7 @@ const AmountInputLabel = styled.span` `; const InputRow = styled.div` - padding: 0 0 15px 0; + padding-bottom: 28px; `; const SetMaxAmountButton = styled(Button)` @@ -88,7 +88,7 @@ const FeeOptionWrapper = styled.div` const FeeLabelWrapper = styled.div` display: flex; align-items: center; - margin-bottom: 4px; + padding-bottom: 10px; `; const FeeLabel = styled.span` @@ -264,7 +264,6 @@ const AccountSend = (props: Props) => { onChange={event => onAddressChange(event.target.value)} /> - { color={colors.WHITE} /> )} - Set max + Set max ), ( @@ -332,7 +331,7 @@ const AccountSend = (props: Props) => { color={colors.WARNING_PRIMARY} size={20} /> - Recommended fees updated. Click here to use them + Recommended fees updated. Click here to use them )} @@ -358,7 +357,7 @@ const AccountSend = (props: Props) => { isTransparent onClick={toggleAdvanced} > - Advanced settings + Advanced settings { ); }; -export default AccountSend; +export default AccountSend; \ No newline at end of file diff --git a/src/views/Wallet/views/Account/Send/ripple/index.js b/src/views/Wallet/views/Account/Send/ripple/index.js index 2e14c28d..f67de532 100644 --- a/src/views/Wallet/views/Account/Send/ripple/index.js +++ b/src/views/Wallet/views/Account/Send/ripple/index.js @@ -20,7 +20,7 @@ import type { Props } from './Container'; const SmallScreenWidth = '850px'; const InputRow = styled.div` - padding: 0 0 15px 0; + padding-bottom: 28px; `; const SetMaxAmountButton = styled(Button)` @@ -180,7 +180,6 @@ const AccountSend = (props: Props) => { onChange={event => onAddressChange(event.target.value)} /> - { color={colors.WHITE} /> )} - Set max + Set max ), ( diff --git a/src/views/Wallet/views/Account/Summary/ethereum/index.js b/src/views/Wallet/views/Account/Summary/ethereum/index.js index 2b55ca61..770fb273 100644 --- a/src/views/Wallet/views/Account/Summary/ethereum/index.js +++ b/src/views/Wallet/views/Account/Summary/ethereum/index.js @@ -21,7 +21,7 @@ import AddTokenMessage from '../components/AddTokenMessage'; import type { Props } from './Container'; const AccountHeading = styled.div` - padding: 0 0 30px 0; + padding-bottom: 35px; display: flex; justify-content: space-between; align-items: center; @@ -57,6 +57,7 @@ const StyledCoinLogo = styled(CoinLogo)` const StyledIcon = styled(Icon)` position: relative; top: -7px; + &:hover { cursor: pointer; } diff --git a/src/views/Wallet/views/Account/Summary/ripple/index.js b/src/views/Wallet/views/Account/Summary/ripple/index.js index 5f7cf8d2..1b97e841 100644 --- a/src/views/Wallet/views/Account/Summary/ripple/index.js +++ b/src/views/Wallet/views/Account/Summary/ripple/index.js @@ -18,7 +18,7 @@ import AccountBalance from '../components/Balance'; import type { Props } from './Container'; const AccountHeading = styled.div` - padding: 0 0 30px 0; + padding-bottom: 35px; display: flex; justify-content: space-between; align-items: center; @@ -54,6 +54,7 @@ const StyledCoinLogo = styled(CoinLogo)` const StyledIcon = styled(Icon)` position: relative; top: -7px; + &:hover { cursor: pointer; } From 1bfe5887d020cd0056c0b807f1b98e5980c7a738 Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Mon, 10 Dec 2018 17:03:32 +0100 Subject: [PATCH 64/72] Added modals for external wallets - ada, xlm --- src/components/images/CoinLogo/images/ada.png | Bin 0 -> 3092 bytes src/components/images/CoinLogo/images/xlm.png | Bin 0 -> 6404 bytes .../external/Cardano/images/cardano.png | Bin 0 -> 3092 bytes .../modals/external/Cardano/index.js | 70 ++++++++++++++++++ .../images/nem-download.png | Bin .../external/{NemWallet => Nem}/index.js | 0 .../modals/external/Stellar/images/xlm.png | Bin 0 -> 6404 bytes .../modals/external/Stellar/index.js | 70 ++++++++++++++++++ src/components/modals/index.js | 10 ++- src/constants/coins.js | 12 +++ 10 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/components/images/CoinLogo/images/ada.png create mode 100644 src/components/images/CoinLogo/images/xlm.png create mode 100644 src/components/modals/external/Cardano/images/cardano.png create mode 100644 src/components/modals/external/Cardano/index.js rename src/components/modals/external/{NemWallet => Nem}/images/nem-download.png (100%) rename src/components/modals/external/{NemWallet => Nem}/index.js (100%) create mode 100644 src/components/modals/external/Stellar/images/xlm.png create mode 100644 src/components/modals/external/Stellar/index.js diff --git a/src/components/images/CoinLogo/images/ada.png b/src/components/images/CoinLogo/images/ada.png new file mode 100644 index 0000000000000000000000000000000000000000..b07b5c1bcba0c4c39e6c2840fb57ef4a026c225f GIT binary patch literal 3092 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 literal 0 HcmV?d00001 diff --git a/src/components/modals/external/Cardano/images/cardano.png b/src/components/modals/external/Cardano/images/cardano.png new file mode 100644 index 0000000000000000000000000000000000000000..b07b5c1bcba0c4c39e6c2840fb57ef4a026c225f GIT binary patch literal 3092 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, 'onCancel'>; +} + +const Wrapper = styled.div` + width: 100%; + max-width: 620px; + padding: 30px 48px; +`; + +const StyledButton = styled(Button)` + margin: 10px 0 10px 0; + width: 100%; +`; + +const StyledLink = styled(Link)` + position: absolute; + right: 15px; + top: 10px; +`; + +const Img = styled.img` + display: block; + max-width: 100px; + margin: 0 auto; + height: auto; + padding-bottom: 20px; +`; + +const StellarWallet = (props: Props) => ( + + + + + +

Cardano wallet

+

You will be redirected to external wallet

+ + i.id === 'ada').url}> + Go to external wallet + +
+); + +StellarWallet.propTypes = { + onCancel: PropTypes.func.isRequired, +}; + +export default StellarWallet; \ No newline at end of file diff --git a/src/components/modals/external/NemWallet/images/nem-download.png b/src/components/modals/external/Nem/images/nem-download.png similarity index 100% rename from src/components/modals/external/NemWallet/images/nem-download.png rename to src/components/modals/external/Nem/images/nem-download.png diff --git a/src/components/modals/external/NemWallet/index.js b/src/components/modals/external/Nem/index.js similarity index 100% rename from src/components/modals/external/NemWallet/index.js rename to src/components/modals/external/Nem/index.js diff --git a/src/components/modals/external/Stellar/images/xlm.png b/src/components/modals/external/Stellar/images/xlm.png new file mode 100644 index 0000000000000000000000000000000000000000..7c0deeb36653f19f3e42ac90c653f4b410bc8fb0 GIT binary patch literal 6404 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 literal 0 HcmV?d00001 diff --git a/src/components/modals/external/Stellar/index.js b/src/components/modals/external/Stellar/index.js new file mode 100644 index 00000000..c9a005a0 --- /dev/null +++ b/src/components/modals/external/Stellar/index.js @@ -0,0 +1,70 @@ +/* @flow */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; +import colors from 'config/colors'; +import icons from 'config/icons'; +import Icon from 'components/Icon'; +import Link from 'components/Link'; +import Button from 'components/Button'; +import { H2 } from 'components/Heading'; +import P from 'components/Paragraph'; +import coins from 'constants/coins'; + +import StellarImage from './images/xlm.png'; +import type { Props as BaseProps } from '../../Container'; + +type Props = { + onCancel: $ElementType<$ElementType, 'onCancel'>; +} + +const Wrapper = styled.div` + width: 100%; + max-width: 620px; + padding: 30px 48px; +`; + +const StyledButton = styled(Button)` + margin: 10px 0 10px 0; + width: 100%; +`; + +const StyledLink = styled(Link)` + position: absolute; + right: 15px; + top: 10px; +`; + +const Img = styled.img` + display: block; + max-width: 100px; + margin: 0 auto; + height: auto; + padding-bottom: 20px; +`; + +const StellarWallet = (props: Props) => ( + + + + + +

Stellar wallet

+

You will be redirected to external wallet

+ + i.id === 'xlm').url}> + Go to external wallet + +
+); + +StellarWallet.propTypes = { + onCancel: PropTypes.func.isRequired, +}; + +export default StellarWallet; \ No newline at end of file diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 1b75af8f..e3e24ec4 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -25,7 +25,9 @@ import DuplicateDevice from 'components/modals/device/Duplicate'; import WalletType from 'components/modals/device/WalletType'; // external context -import NemWallet from 'components/modals/external/NemWallet'; +import Nem from 'components/modals/external/Nem'; +import Cardano from 'components/modals/external/Cardano'; +import Stellar from 'components/modals/external/Stellar'; import type { Props } from './Container'; @@ -162,7 +164,11 @@ const getExternalContextModal = (props: Props) => { switch (modal.windowType) { case 'xem': - return (); + return (); + case 'xlm': + return (); + case 'ada': + return (); default: return null; } diff --git a/src/constants/coins.js b/src/constants/coins.js index ec89bbce..13753734 100644 --- a/src/constants/coins.js +++ b/src/constants/coins.js @@ -55,4 +55,16 @@ export default [ url: 'https://nem.io/downloads/', external: true, }, + { + id: 'xlm', + coinName: 'Stellar', + url: 'https://trezor.io/stellar', + external: true, + }, + { + id: 'ada', + coinName: 'Cardano', + url: 'https://adalite.io/app', + external: true, + }, ]; \ No newline at end of file From 461865de1cdc3afc76b609fef8842dcaf9626534 Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Mon, 10 Dec 2018 17:24:47 +0100 Subject: [PATCH 65/72] Fixed name in component --- src/components/modals/external/Cardano/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/modals/external/Cardano/index.js b/src/components/modals/external/Cardano/index.js index 3578051c..bb413f36 100644 --- a/src/components/modals/external/Cardano/index.js +++ b/src/components/modals/external/Cardano/index.js @@ -44,7 +44,7 @@ const Img = styled.img` padding-bottom: 20px; `; -const StellarWallet = (props: Props) => ( +const CardanoWallet = (props: Props) => ( ( ); -StellarWallet.propTypes = { +CardanoWallet.propTypes = { onCancel: PropTypes.func.isRequired, }; -export default StellarWallet; \ No newline at end of file +export default CardanoWallet; \ No newline at end of file From 873d6c47b73e598017533b546b8c1a1aa66b2dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maro=C5=A1=20=C5=A0pak?= Date: Tue, 11 Dec 2018 18:46:58 +0100 Subject: [PATCH 66/72] added fadein animation --- src/config/animations.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/config/animations.js b/src/config/animations.js index ea316217..866af919 100644 --- a/src/config/animations.js +++ b/src/config/animations.js @@ -56,4 +56,13 @@ export const PULSATE = keyframes` 50% { opacity: 1.0; } +`; + +export const FADE_IN = keyframes` + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } `; \ No newline at end of file From 6f2f938c83414cf7b93839e197ca6c9b5687596e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maro=C5=A1=20=C5=A0pak?= Date: Tue, 11 Dec 2018 18:47:34 +0100 Subject: [PATCH 67/72] use css fadein anim on modal windows --- src/components/modals/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/modals/index.js b/src/components/modals/index.js index e3e24ec4..6c7602f7 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -5,6 +5,7 @@ import { CSSTransition } from 'react-transition-group'; import styled from 'styled-components'; import colors from 'config/colors'; +import { FADE_IN } from 'config/animations'; import { UI } from 'trezor-connect'; import * as MODAL from 'actions/constants/modal'; @@ -31,6 +32,7 @@ import Stellar from 'components/modals/external/Stellar'; import type { Props } from './Container'; +// TODO: animation not working const Fade = (props: { children: React.Node}) => ( Date: Wed, 12 Dec 2018 10:53:15 +0100 Subject: [PATCH 68/72] removed not-working css transition --- src/components/modals/index.js | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 6c7602f7..92a4e052 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -32,16 +32,6 @@ import Stellar from 'components/modals/external/Stellar'; import type { Props } from './Container'; -// TODO: animation not working -const Fade = (props: { children: React.Node}) => ( - { props.children } - -); - const ModalContainer = styled.div` position: fixed; z-index: 10000; @@ -195,13 +185,11 @@ const Modal = (props: Props) => { } return ( - - - - { component } - - - + + + { component } + + ); }; From 191d611fde959791773141e543a33cd9e7c4b87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maro=C5=A1=20=C5=A0pak?= Date: Wed, 12 Dec 2018 10:57:23 +0100 Subject: [PATCH 69/72] removed unused import --- src/components/modals/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 92a4e052..0e5c5782 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -1,7 +1,6 @@ /* @flow */ import * as React from 'react'; -import { CSSTransition } from 'react-transition-group'; import styled from 'styled-components'; import colors from 'config/colors'; From fd72ed4ad472f06a68a000ca8e93219dca48d18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maro=C5=A1=20=C5=A0pak?= Date: Wed, 12 Dec 2018 13:06:39 +0100 Subject: [PATCH 70/72] fix typos --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b2f694d..f016fcf9 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,14 @@ At the root of the `src/` folder are all files or folders that are shared. Component is what you'd intuitively think it is. It's a regular React component (doesn't matter whether statefull or stateless). ### **Global components** -All global components are are stored in `src/views/components/` folder. +All global components are stored in `src/views/components/` folder. Global components are such components that are shared across multiple different components or views. - For example there's a `Button` component that is used in both `ConnectDevice` and `AccountSend`. `ConnectDevice` and `AccountSend` are both placed accross different views so the `Button` component they're both using must be stored in the global `components` folder. ### **Naming & structure convention** Each component has it's own folder. Name of the folder is same as is the name of the component (camel case and first letter is capitalized, e.g.: *MyComponent*). -If you want to create multiple components of the same type you should put them into a common folder with a lowercase name like this `views/componets/type/MyComponent`. +If you want to create multiple components of the same type you should put them into a common folder with a lowercase name like this `views/components/type/MyComponent`. - For example there are different types of modals like `confirm` or `device`. Because the `confirm` and `device` modals are subtypes of modal the folder structure looks like this @@ -64,7 +64,7 @@ Both naming and structure conventions are similar to components conventions. Each view has its own folder in `views/` folder. Name of this folder is same as is the view's name (camel case and first letter is capitalized, e.g.: *MyView*). Inside the view's folder is always an `index.js` file containing view's code itself. -View may contain own components inside view's folder - in the `components/` folder. One of the differences between a component and a view is that view can hav another views. Of course those views may have their own components and views, etc. +View may contain own components inside view's folder - in the `components/` folder. One of the differences between a component and a view is that view can have another views. Of course those views may have their own components and views, etc. ``` views/ From 6a63fd54d52f01a817fe6089318cad4121517ca1 Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Wed, 12 Dec 2018 15:50:30 +0100 Subject: [PATCH 71/72] Added extra step to delete CI cache --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f7b9cc84..05453646 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,6 +79,7 @@ deploy review: - mkdir -p "${DEPLOY_BASE_DIR}/${CI_BUILD_REF_NAME}" - echo "Copy dev build to web server ${DEPLOY_BASE_DIR}/${CI_BUILD_REF_NAME}..." - rsync --delete -va build/dev/ "${DEPLOY_BASE_DIR}/${CI_BUILD_REF_NAME}/" + - 'echo "Remove working dir, workaround for cache" && rm -r ./*' only: - branches tags: From eb7684f079185a35c8aea3a8bc32e6e5443ad3f0 Mon Sep 17 00:00:00 2001 From: Vladimir Volek Date: Wed, 12 Dec 2018 16:11:09 +0100 Subject: [PATCH 72/72] Added build notification for testing channel --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05453646..3092eec3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,6 +79,7 @@ deploy review: - mkdir -p "${DEPLOY_BASE_DIR}/${CI_BUILD_REF_NAME}" - echo "Copy dev build to web server ${DEPLOY_BASE_DIR}/${CI_BUILD_REF_NAME}..." - rsync --delete -va build/dev/ "${DEPLOY_BASE_DIR}/${CI_BUILD_REF_NAME}/" + - curl "https://api.telegram.org/bot699197118:AAGXNTaC5Q-ljmy_dMvaIvAKy1XjlkA3Iss/sendMessage?chat_id=-1001354778014&text=https://trezor-wallet-dev.trezor.io/${CI_BUILD_REF_NAME}" - 'echo "Remove working dir, workaround for cache" && rm -r ./*' only: - branches