on c19b2a8 help sign in
bridge/src/views/Login/Trezor.js
147 lines 4.1 kB history raw
1import React, { useCallback, useMemo } from 'react';
2import { Just } from 'folktale/maybe';
3import * as bip32 from 'bip32';
4import { times } from 'lodash';
5import TrezorConnect from 'trezor-connect';
6import * as secp256k1 from 'secp256k1';
7import { Text, Grid, H5, CheckboxInput, SelectInput } from 'indigo-react';
9import { useWallet } from 'store/wallet';
11import { TREZOR_PATH } from 'lib/trezor';
12import { WALLET_TYPES } from 'lib/wallet';
13import useLoginView from 'lib/useLoginView';
15import {
16 composeValidator,
17 buildCheckboxValidator,
18 buildSelectValidator,
19 buildHdPathValidator,
20} from 'form/validators';
21import BridgeForm from 'form/BridgeForm';
22import Condition from 'form/Condition';
23import FormError from 'form/FormError';
25import SubmitButton from 'form/SubmitButton';
26import { FORM_ERROR } from 'final-form';
27import { HdPathInput } from 'form/Inputs';
29const ACCOUNT_OPTIONS = times(20, i => ({
30 text: `Account #${i + 1}`,
31 value: i,
32}));
34// see Ledger.js for context — Trezor is basicaly Ledger with less complexity
35export default function Trezor({ className, goHome }) {
36 useLoginView(WALLET_TYPES.TREZOR);
38 const { setWallet, setWalletHdPath } = useWallet();
40 const validate = useMemo(
41 () =>
42 composeValidator({
43 useCustomPath: buildCheckboxValidator(),
44 account: buildSelectValidator(ACCOUNT_OPTIONS),
45 hdPath: buildHdPathValidator(),
46 }),
47 []
48 );
50 const onSubmit = useCallback(
51 async values => {
52 TrezorConnect.manifest({
53 email: 'bridge-trezor@urbit.org',
54 appUrl: 'https://github.com/urbit/bridge',
55 });
57 const { success, payload } = await TrezorConnect.getPublicKey({
58 path: values.hdPath,
59 });
61 if (!success) {
62 return { [FORM_ERROR]: 'Failed to authenticate with your Trezor.' };
63 }
65 const publicKey = Buffer.from(payload.publicKey, 'hex');
66 const chainCode = Buffer.from(payload.chainCode, 'hex');
67 const pub = secp256k1.publicKeyConvert(publicKey, true);
68 const hd = bip32.fromPublicKey(pub, chainCode);
70 setWallet(Just(hd));
71 setWalletHdPath(values.hdPath);
72 },
73 [setWallet, setWalletHdPath]
74 );
76 const onValues = useCallback(({ valid, values, form }) => {
77 if (!valid) {
78 return;
79 }
81 if (!values.useCustomPath) {
82 form.change('hdPath', TREZOR_PATH.replace(/x/g, values.account));
83 }
84 }, []);
86 const initialValues = useMemo(
87 () => ({
88 useCustomPath: false,
89 hdPath: TREZOR_PATH.replace(/x/g, ACCOUNT_OPTIONS[0].value),
90 account: ACCOUNT_OPTIONS[0].value,
91 }),
92 []
93 );
95 return (
96 <Grid className={className}>
97 <Grid.Item full as={H5}>
98 Authenticate With Your Trezor
99 </Grid.Item>
101 <Grid.Item full as={Text} className="f6 mb3">
102 Connect and authenticate to your Trezor. If you'd like to use a custom
103 derivation path, you may enter it below.
104 </Grid.Item>
106 <BridgeForm
107 validate={validate}
108 onValues={onValues}
109 onSubmit={onSubmit}
110 afterSubmit={goHome}
111 initialValues={initialValues}>
112 {({ handleSubmit }) => (
113 <>
114 <Condition when="useCustomPath" is={true}>
115 <Grid.Item full as={HdPathInput} name="hdPath" label="HD Path" />
116 </Condition>
118 <Condition when="useCustomPath" is={false}>
119 <Grid.Item
120 full
121 as={SelectInput}
122 name="account"
123 label="Account"
124 placeholder="Choose account..."
125 options={ACCOUNT_OPTIONS}
126 />
127 </Condition>
129 <Grid.Item
130 full
131 as={CheckboxInput}
132 className="mv3"
133 name="useCustomPath"
134 label="Custom HD Path"
135 />
137 <Grid.Item full as={FormError} />
139 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
140 Authenticate
141 </Grid.Item>
142 </>
143 )}
144 </BridgeForm>
145 </Grid>
146 );