import React, { useCallback, useMemo } from 'react'; import cn from 'classnames'; import { Just } from 'folktale/maybe'; import { P, Text, Grid, H5, CheckboxInput, SelectInput } from 'indigo-react'; import { times } from 'lodash'; import * as bip32 from 'bip32'; import Transport from '@ledgerhq/hw-transport-u2f'; import Eth from '@ledgerhq/hw-app-eth'; import * as secp256k1 from 'secp256k1'; import { useWallet } from 'store/wallet'; import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH, chopHdPrefix, addHdPrefix, } from 'lib/ledger'; import { WALLET_TYPES } from 'lib/wallet'; import useLoginView from 'lib/useLoginView'; import useBreakpoints from 'lib/useBreakpoints'; import { composeValidator, buildCheckboxValidator, buildHdPathValidator, buildSelectValidator, } from 'form/validators'; import BridgeForm from 'form/BridgeForm'; import FormError from 'form/FormError'; import SubmitButton from 'form/SubmitButton'; import Condition from 'form/Condition'; import { FORM_ERROR } from 'final-form'; import { HdPathInput } from 'form/Inputs'; const PATH_OPTIONS = [ { text: 'Ledger Live', value: LEDGER_LIVE_PATH }, { text: 'Ledger Legacy', value: LEDGER_LEGACY_PATH }, ]; const ACCOUNT_OPTIONS = times(20, i => ({ text: `Account #${i + 1}`, value: i, })); export default function Ledger({ className, goHome }) { useLoginView(WALLET_TYPES.LEDGER); const { setWallet, setWalletHdPath } = useWallet(); const validate = useMemo( () => composeValidator({ useCustomPath: buildCheckboxValidator(), derivationpath: buildSelectValidator(PATH_OPTIONS), account: buildSelectValidator(ACCOUNT_OPTIONS), hdpath: buildHdPathValidator(), }), [] ); const onSubmit = useCallback( async values => { const transport = await Transport.create(); const eth = new Eth(transport); const path = chopHdPrefix(values.hdpath); try { const info = await eth.getAddress(path, false, true); const publicKey = Buffer.from(info.publicKey, 'hex'); const chainCode = Buffer.from(info.chainCode, 'hex'); const pub = secp256k1.publicKeyConvert(publicKey, true); const hd = bip32.fromPublicKey(pub, chainCode); setWallet(Just(hd)); setWalletHdPath(addHdPrefix(values.hdpath)); } catch (error) { return { [FORM_ERROR]: error.message }; } }, [setWallet, setWalletHdPath] ); const onValues = useCallback(({ valid, values, form }) => { if (!valid) { return; } // when the base path pattern or the account index changes // update the hd path in our input if (!values.useCustomPath) { form.change( 'hdpath', values.derivationpath.replace(/x/g, values.account) ); } }, []); const initialValues = useMemo( () => ({ useCustomPath: false, hdpath: PATH_OPTIONS[0].value.replace(/x/g, ACCOUNT_OPTIONS[0].value), derivationpath: PATH_OPTIONS[0].value, account: ACCOUNT_OPTIONS[0].value, }), [] ); const full = useBreakpoints([true, true, false]); const half = useBreakpoints([false, false, true]); const isHTTPS = document.location.protocol === 'https:'; // when not on https, tell user how to get there const renderHTTP = () => ( <> Running on HTTP? To authenticate and sign transactions with a Ledger, Bridge must be serving over HTTPS on localhost. You can do this via the following:
  • Install{' '} mkcert
  • Install a local certificate authority via mkcert -install
  • In your bridge directory, generate a certificate valid for localhost via mkcert localhost. This will produce two files: localhost.pem, the local certificate, and localhost-key.pem , its corresponding private key.
  • Run python bridge-https.py
  • Return to this page.
  • ); const renderHTTPS = () => ( <> Authenticate With Your Ledger Connect and authenticate to your Ledger, and then open the "Ethereum" application. If you're running on older firmware, make sure the "browser support" option is turned on. To sign transactions, you'll also need to enable the "contract data" option. {({ handleSubmit }) => ( <> Authenticate )} ); return ( {isHTTPS ? renderHTTPS() : renderHTTP()} ); }