import React, { useCallback, useEffect } from 'react'; import cn from 'classnames'; import { Just, Nothing } from 'folktale/maybe'; import { P, Text, Input, 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 { ForwardButton } from 'components/Buttons'; import { useCheckboxInput, useHdPathInput, useSelectInput, } from 'lib/useInputs'; 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'; 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 }) { useLoginView(WALLET_TYPES.LEDGER); const { setWallet, setWalletHdPath } = useWallet(); // derivation path input const [derivationPathInput, { data: basePathPattern }] = useSelectInput({ name: 'derivationpath', label: 'Derivation Path', placeholder: 'Choose path pattern...', options: PATH_OPTIONS, }); // account input const [accountInput, { data: accountIndex }] = useSelectInput({ name: 'account', label: 'Account', placeholder: 'Choose account...', options: ACCOUNT_OPTIONS, }); // custom toggle const [customPathInput, { data: useCustomPath }] = useCheckboxInput({ name: 'customPath', label: 'Custom HD Path', autoComplete: 'off', initialValue: false, }); // hd path input const [ hdPathInput, { data: hdPath }, { setValue: setHdPath }, ] = useHdPathInput({ name: 'hdpath', label: 'HD Path', initialValue: basePathPattern.replace(/x/g, 0), }); const pollDevice = useCallback(async () => { const transport = await Transport.create(); const eth = new Eth(transport); const path = chopHdPrefix(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(hdPath)); } catch (error) { console.error(error); setWallet(Nothing()); } }, [hdPath, setWallet, setWalletHdPath]); // when the base path pattern or the account index changes // update the hd path in our input useEffect(() => { if (useCustomPath) { // updated by useForm } else { setHdPath(basePathPattern.replace(/x/g, accountIndex)); } }, [useCustomPath, setHdPath, basePathPattern, accountIndex]); 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. {useCustomPath && } {!useCustomPath && ( <> )} Authenticate ); return ( {isHTTPS ? renderHTTPS() : renderHTTP()} ); }