on aee35bb help sign in
bridge/src/views/Ledger.js
220 lines 6.6 kB history raw
1import * as bip32 from 'bip32';
2import React from 'react';
3import Maybe from 'folktale/maybe';
4import { Button } from '../components/old/Base';
5import { H1, P, H2 } from '../components/old/Base';
6import { Input, InnerLabel, InnerLabelDropdown } from '../components/old/Base';
7import Transport from '@ledgerhq/hw-transport-u2f';
8import Eth from '@ledgerhq/hw-app-eth';
9import * as secp256k1 from 'secp256k1';
11import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../lib/ledger';
12import { ROUTE_NAMES } from '../lib/routeNames';
13import { withHistory } from '../store/history';
14import { compose } from '../lib/lib';
15import { withWallet } from '../store/wallet';
16import View from 'components/View';
18const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
20const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
22class Ledger extends React.Component {
23 constructor(props) {
24 super(props);
26 this.state = {
27 basePath: LEDGER_LIVE_PATH,
28 hdpath: LEDGER_LIVE_PATH.replace(/x/g, 0),
29 account: 0,
30 };
32 this.pollDevice = this.pollDevice.bind(this);
33 this.handleHdPathInput = this.handleHdPathInput.bind(this);
34 this.handlePathSelection = this.handlePathSelection.bind(this);
35 this.handleAccountSelection = this.handleAccountSelection.bind(this);
36 this.updateHdPath = this.updateHdPath.bind(this);
37 }
39 handlePathSelection(basePath) {
40 this.updateHdPath(basePath, this.state.account);
41 }
43 handleAccountSelection(account) {
44 this.updateHdPath(this.state.basePath, account);
45 }
47 updateHdPath(basePath, account) {
48 let hdpath = this.state.hdpath;
49 if (basePath !== 'custom') {
50 hdpath = basePath.replace(/x/g, account);
51 }
52 this.setState({ basePath, account, hdpath });
53 }
55 handleHdPathInput(hdpath) {
56 this.setState({ hdpath });
57 }
59 async pollDevice() {
60 const { setWallet, setWalletHdPath } = this.props;
61 const { hdpath } = this.state;
63 const transport = await Transport.create();
64 const eth = new Eth(transport);
65 const path = chopHdPrefix(hdpath);
67 eth.getAddress(path, false, true).then(
68 info => {
69 const publicKey = Buffer.from(info.publicKey, 'hex');
70 const chainCode = Buffer.from(info.chainCode, 'hex');
71 const pub = secp256k1.publicKeyConvert(publicKey, true);
72 const hd = bip32.fromPublicKey(pub, chainCode);
73 setWallet(Maybe.Just(hd));
74 setWalletHdPath(addHdPrefix(hdpath));
75 },
76 _ => {
77 setWallet(Maybe.Nothing());
78 }
79 );
80 }
82 render() {
83 const { history, wallet } = this.props;
84 const { basePath, hdpath, account } = this.state;
85 const { handlePathSelection, handleAccountSelection } = this;
87 const pathOptions = [
88 { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
89 { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
90 {
91 title: 'Custom path',
92 value: 'custom',
93 },
94 ];
95 let basePathTitle = pathOptions.find(o => o.value === basePath).title;
97 let accountOptions = [];
98 for (let i = 0; i < 20; i++) {
99 accountOptions.push({
100 title: 'Account #' + (i + 1),
101 value: i,
102 });
103 }
104 let accountTitle = accountOptions.find(o => o.value === account).title;
106 const basePathSelection = (
107 <InnerLabelDropdown
108 className="mt-8"
109 options={pathOptions}
110 handleUpdate={handlePathSelection}
111 title={'Derivation path'}
112 currentSelectionTitle={basePathTitle}
113 fullWidth={true}
114 />
115 );
117 const truePathSelection =
118 basePath === 'custom' ? (
119 <Input
120 className="pt-8 mt-4 text-mono"
121 prop-size="md"
122 prop-format="innerLabel"
123 name="hdpath"
124 value={addHdPrefix(hdpath)}
125 autocomplete="off"
126 onChange={this.handleHdPathInput}>
127 <InnerLabel>{'HD Path'}</InnerLabel>
128 </Input>
129 ) : (
130 <InnerLabelDropdown
131 className="mt-4"
132 prop-size="md"
133 prop-format="innerLabel"
134 options={accountOptions}
135 handleUpdate={handleAccountSelection}
136 title={'Account'}
137 currentSelectionTitle={accountTitle}
138 fullWidth={true}
139 />
140 );
142 return (
143 <View>
144 <H1>{'Authenticate With Your Ledger'}</H1>
146 <H2>{'Running on HTTPS?'}</H2>
148 <P>
149 {`Connect and authenticate to your Ledger, and then open the
150 "Ethereum" application. If you're running on older firmware,
151 make sure the "browser support" option is turned on. To sign
152 transactions, you'll also need to enable the "contract data"
153 option.`}
154 </P>
156 <P>
157 {`If you'd like to use a custom derivation path, you may enter
158 it below.`}
159 </P>
161 <H2>{'Running on HTTP?'}</H2>
163 <P>{`To authenticate and sign transactions with a Ledger, Bridge
164 must be serving over HTTPS on localhost. You can do this via the
165 following:`}</P>
167 <ol className={'measure-md'}>
168 <li className={'mt-4'}>
169 {'Install'}{' '}
170 <a target={'_blank'} href={'https://github.com/FiloSottile/mkcert'}>
171 {'mkcert'}
172 </a>
173 </li>
174 <li className={'mt-4'}>
175 {'Install a local certificate authority via '}
176 <code>{'mkcert -install'}</code>
177 </li>
178 <li className={'mt-4'}>
179 {'In your '}
180 <code>{'bridge'}</code>
181 {' directory, generate a certificate valid for localhost via '}{' '}
182 <code>{'mkcert localhost'}</code>
183 {'.'}
184 {'This will produce two files: '}
185 <code>{'localhost.pem'}</code> {', the local certificate, and '}
186 <code>{'localhost-key.pem'}</code>
187 {', its corresponding private key.'}
188 </li>
189 <li className={'mt-4'}>
190 {'Run '}
191 <code>{'python bridge-https.py'}</code>
192 </li>
193 </ol>
195 {basePathSelection}
196 {truePathSelection}
198 <Button
199 prop-size={'wide lg'}
200 className={'mt-8'}
201 onClick={this.pollDevice}>
202 {'Authenticate →'}
203 </Button>
205 <Button
206 className={'mt-8'}
207 prop-size={'wide lg'}
208 disabled={Maybe.Nothing.hasInstance(wallet)}
209 onClick={() => history.popAndPush(ROUTE_NAMES.POINTS)}>
210 {'Continue →'}
211 </Button>
212 </View>
213 );
214 }
217export default compose(
218 withHistory,
219 withWallet
220)(Ledger);