import BigNumber from 'bignumber.js'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn, } from 'material-ui/Table'; import TextField from 'material-ui/TextField'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import {Blockchain} from 'ts/blockchain'; import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; import {Dispatcher} from 'ts/redux/dispatcher'; import {colors} from 'ts/utils/colors'; import {configs} from 'ts/utils/configs'; import {constants} from 'ts/utils/constants'; import {utils} from 'ts/utils/utils'; const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`; enum LedgerSteps { CONNECT, SELECT_ADDRESS, } interface LedgerConfigDialogProps { isOpen: boolean; toggleDialogFn: (isOpen: boolean) => void; dispatcher: Dispatcher; blockchain: Blockchain; networkId: number; } interface LedgerConfigDialogState { didConnectFail: boolean; stepIndex: LedgerSteps; userAddresses: string[]; addressBalances: BigNumber[]; derivationPath: string; derivationErrMsg: string; } export class LedgerConfigDialog extends React.Component { constructor(props: LedgerConfigDialogProps) { super(props); this.state = { didConnectFail: false, stepIndex: LedgerSteps.CONNECT, userAddresses: [], addressBalances: [], derivationPath: configs.DEFAULT_DERIVATION_PATH, derivationErrMsg: '', }; } public render() { const dialogActions = [ , ]; const dialogTitle = this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address'; return (
{this.state.stepIndex === LedgerSteps.CONNECT && this._renderConnectStep() } {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && this._renderSelectAddressStep() }
); } private _renderConnectStep() { return (
Follow these instructions before proceeding:
  1. Connect your Ledger Nano S & Open the Ethereum application
  2. Verify that Browser Support is enabled in Settings
  3. If no Browser Support is found in settings, verify that you have{' '} Firmware >1.2
{this.state.didConnectFail &&
Failed to connect. Follow the instructions and try again.
}
); } private _renderSelectAddressStep() { return (
Address Balance {this._renderAddressTableRows()}
); } private _renderAddressTableRows() { const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { const balance = this.state.addressBalances[i]; const addressTooltipId = `address-${userAddress}`; const balanceTooltipId = `balance-${userAddress}`; const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; // We specifically prefix kovan ETH. // TODO: We should probably add prefixes for all networks const isKovanNetwork = networkName === 'Kovan'; const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; return (
{userAddress}
{userAddress}
{balanceString}
{balanceString}
); }); return rows; } private _onClose() { this.setState({ didConnectFail: false, }); const isOpen = false; this.props.toggleDialogFn(isOpen); } private _onAddressSelected(selectedRowIndexes: number[]) { const selectedRowIndex = selectedRowIndexes[0]; this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); const selectedAddress = this.state.userAddresses[selectedRowIndex]; const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; this.props.dispatcher.updateUserAddress(selectedAddress); this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); this.setState({ stepIndex: LedgerSteps.CONNECT, }); const isOpen = false; this.props.toggleDialogFn(isOpen); } private async _onFetchAddressesForDerivationPathAsync() { const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); if (currentlySetPath === this.state.derivationPath) { return; } this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); const didSucceed = await this._fetchAddressesAndBalancesAsync(); if (!didSucceed) { this.setState({ derivationErrMsg: 'Failed to connect to Ledger.', }); } return didSucceed; } private async _fetchAddressesAndBalancesAsync() { let userAddresses: string[]; const addressBalances: BigNumber[] = []; try { userAddresses = await this._getUserAddressesAsync(); for (const address of userAddresses) { const balance = await this.props.blockchain.getBalanceInEthAsync(address); addressBalances.push(balance); } } catch (err) { utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); this.setState({ didConnectFail: true, }); return false; } this.setState({ userAddresses, addressBalances, }); return true; } private _onDerivationPathChanged(e: any, derivationPath: string) { let derivationErrMsg = ''; if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { derivationErrMsg = 'Must be valid Ethereum path.'; } this.setState({ derivationPath, derivationErrMsg, }); } private async _onConnectLedgerClickAsync() { const didSucceed = await this._fetchAddressesAndBalancesAsync(); if (didSucceed) { this.setState({ stepIndex: LedgerSteps.SELECT_ADDRESS, }); } return didSucceed; } private async _getUserAddressesAsync(): Promise { let userAddresses: string[]; userAddresses = await this.props.blockchain.getUserAccountsAsync(); if (_.isEmpty(userAddresses)) { throw new Error('No addresses retrieved.'); } return userAddresses; } }