on master help sign in
Merge pull request #431 from urbit/lf/auth-token
2c3486cFang
package.json+1−1
@@ -81,7 +81,7 @@
81 "checksum": "cd build && find . -exec shasum -a256 {} ';' > ../checksums.txt && cd ..",
81 "checksum": "cd build && find . -exec shasum -a256 {} ';' > ../checksums.txt && cd ..",
82 "zip": "zip -ur bridge-$npm_package_version.zip README.md bridge-https.py checksums.txt && cd build && zip -ur ../bridge-$npm_package_version.zip ./* && cd ..",
82 "zip": "zip -ur bridge-$npm_package_version.zip README.md bridge-https.py checksums.txt && cd build && zip -ur ../bridge-$npm_package_version.zip ./* && cd ..",
83 "release": "npm install && npm run build && npm run checksum && npm run zip",
83 "release": "npm install && npm run build && npm run checksum && npm run zip",
84− "pilot:ganache": "ganache-cli --blockTime 1 --networkId 1 --host '0.0.0.0' -m 'benefit crew supreme gesture quantum web media hazard theory mercy wing kitten' > /dev/null &",
84+ "pilot:ganache": "ganache-cli --blockTime 1 --networkId 5 --host '0.0.0.0' -m 'benefit crew supreme gesture quantum web media hazard theory mercy wing kitten' > /dev/null &",
85 "pilot:setup": "npm run pilot:ganache && truffle deploy",
85 "pilot:setup": "npm run pilot:ganache && truffle deploy",
86 "pilot": "HTTPS=true npm-run-all pilot:setup start pilot:cleanup --continue-on-error",
86 "pilot": "HTTPS=true npm-run-all pilot:setup start pilot:cleanup --continue-on-error",
87 "pilot-invites": "WITH_TEST_STATE=INVITES npm run pilot",
87 "pilot-invites": "WITH_TEST_STATE=INVITES npm run pilot",
src/lib/authToken.jsnew file+44
@@ -0,0 +1,44 @@
1+import * as secp256k1 from 'secp256k1';
2+
3+import { keccak256, WALLET_TYPES } from './wallet';
4+import { ledgerSignMessage } from './ledger';
5+import { trezorSignMessage } from './trezor';
6+
7+const MESSAGE = 'Bridge Authentication Token';
8+
9+function signMessage(privateKey) {
10+ const { signature } = secp256k1.sign(
11+ keccak256('\x19Ethereum Signed Message:\n' + MESSAGE.length + MESSAGE),
12+ privateKey
13+ );
14+ // add key recovery parameter
15+ const ethSignature = new Uint8Array(65);
16+ ethSignature.set(signature);
17+ const v = (ethSignature[32] & 1) + 27;
18+ ethSignature[64] = v;
19+
20+ return ethSignature;
21+}
22+
23+export const getAuthToken = async ({
24+ wallet,
25+ walletType,
26+ walletHdPath,
27+ web3,
28+}) => {
29+ if (walletType === WALLET_TYPES.METAMASK) {
30+ return web3.eth.personal.sign(MESSAGE, wallet.address, '');
31+ }
32+ if (walletType === WALLET_TYPES.LEDGER) {
33+ return ledgerSignMessage(MESSAGE, walletHdPath);
34+ }
35+ if (walletType === WALLET_TYPES.TREZOR) {
36+ return trezorSignMessage(MESSAGE, walletHdPath);
37+ }
38+
39+ const signature = signMessage(wallet.privateKey);
40+
41+ const token = `0x${Buffer.from(signature).toString('hex')}`;
42+
43+ return token;
44+};
src/lib/ledger.js+19−2
@@ -8,11 +8,11 @@ export const chopHdPrefix = str =>
8 str.slice(0, 2) === 'm/' ? str.slice(2) : str;
8 str.slice(0, 2) === 'm/' ? str.slice(2) : str;
9export const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
9export const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
10
10
11−export const ledgerSignTransaction = async (txn, hdpath) => {
11+export const ledgerSignTransaction = async (txn, hdPath) => {
12 const transport = await Transport.create();
12 const transport = await Transport.create();
13 const eth = new Eth(transport);
13 const eth = new Eth(transport);
14
14
15− const path = chopHdPrefix(hdpath);
15+ const path = chopHdPrefix(hdPath);
16 const serializedTx = txn.serialize().toString('hex');
16 const serializedTx = txn.serialize().toString('hex');
17 const sig = await eth.signTransaction(path, serializedTx);
17 const sig = await eth.signTransaction(path, serializedTx);
18
18
@@ -22,3 +22,20 @@ export const ledgerSignTransaction = async (txn, hdpath) => {
22
22
23 return txn;
23 return txn;
24};
24};
25+
26+export const ledgerSignMessage = async (message, hdPath) => {
27+ const transport = await Transport.create();
28+ const eth = new Eth(transport);
29+
30+ const path = chopHdPrefix(hdPath);
31+ const { r, s, v } = await eth.signPersonalMessage(
32+ path,
33+ Buffer.from(message).toString('hex')
34+ );
35+ const ethSignature = new Uint8Array(65);
36+ ethSignature.set(Buffer.from(r, 'hex'));
37+ ethSignature.set(Buffer.from(s, 'hex'), 32);
38+ ethSignature[64] = v;
39+
40+ return `0x${Buffer.from(ethSignature).toString('hex')}`;
41+};
src/lib/network.js+12−1
@@ -16,4 +16,15 @@ const renderNetworkType = network =>
16 ? 'Local Node'
16 ? 'Local Node'
17 : 'Offline';
17 : 'Offline';
18
18
19+const chainIdToNetworkType = chainId => {
20+ switch (chainId) {
21+ case '0x1':
22+ return NETWORK_TYPES.MAINNET;
23+ case '0x3':
24+ return NETWORK_TYPES.ROPSTEN;
25+ default:
26+ return NETWORK_TYPES.LOCAL;
27+ }
28+};
29+
19−export { NETWORK_TYPES, renderNetworkType };
30+export { NETWORK_TYPES, renderNetworkType, chainIdToNetworkType };
src/lib/trezor.js+15
@@ -36,4 +36,19 @@ const trezorSignTransaction = async (txn, hdpath) => {
36 return txn;
36 return txn;
37};
37};
38
38
39+export const trezorSignMessage = async (message, hdPath) => {
40+ const sig = await TrezorConnect.signMessage({
41+ path: hdPath,
42+ message,
43+ });
44+
45+ if (!sig.success) {
46+ throw new Error(sig.payload.error);
47+ }
48+
49+ const { signature } = sig.payload;
50+
51+ return signature;
52+};
53+
39export { TREZOR_PATH, trezorSignTransaction };
54export { TREZOR_PATH, trezorSignTransaction };
src/lib/wallet.js+6
@@ -22,6 +22,12 @@ export const WALLET_TYPES = {
22 METAMASK: Symbol('METAMASK'),
22 METAMASK: Symbol('METAMASK'),
23};
23};
24
24
25+export const NONCUSTODIAL_WALLETS = new Set([
26+ WALLET_TYPES.METAMASK,
27+ WALLET_TYPES.TREZOR,
28+ WALLET_TYPES.LEDGER,
29+]);
30+
25export function EthereumWallet(privateKey) {
31export function EthereumWallet(privateKey) {
26 this.privateKey = privateKey;
32 this.privateKey = privateKey;
27 this.publicKey = secp256k1.publicKeyCreate(this.privateKey);
33 this.publicKey = secp256k1.publicKeyCreate(this.privateKey);
src/store/network.js+22−10
@@ -4,6 +4,7 @@ import React, {
4 useContext,
4 useContext,
5 useMemo,
5 useMemo,
6 useState,
6 useState,
7+ useEffect,
7} from 'react';
8} from 'react';
8import { Just, Nothing } from 'folktale/maybe';
9import { Just, Nothing } from 'folktale/maybe';
9import * as azimuth from 'azimuth-js';
10import * as azimuth from 'azimuth-js';
@@ -11,25 +12,36 @@ import Web3 from 'web3';
11import { includes } from 'lodash';
12import { includes } from 'lodash';
12
13
13import { CONTRACT_ADDRESSES } from '../lib/contracts';
14import { CONTRACT_ADDRESSES } from '../lib/contracts';
14−import { NETWORK_TYPES } from '../lib/network';
15+import { NETWORK_TYPES, chainIdToNetworkType } from '../lib/network';
15import { isDevelopment } from '../lib/flags';
16import { isDevelopment } from '../lib/flags';
16import { BRIDGE_ERROR } from '../lib/error';
17import { BRIDGE_ERROR } from '../lib/error';
17
18
18function _useNetwork(initialNetworkType = null) {
19function _useNetwork(initialNetworkType = null) {
19− const [networkType, _setNetworkType] = useState(initialNetworkType);
20+ const [networkType, setNetworkType] = useState(initialNetworkType);
20
21
21− const setNetworkType = networkType => {
22+ const [metamask, setMetamask] = useState(false);
22− if (!includes(NETWORK_TYPES, networkType)) {
23+
23− throw new Error(BRIDGE_ERROR.INVALID_NETWORK_TYPE);
24+ useEffect(() => {
25+ (async () => {
26+ if (window.ethereum) {
27+ try {
28+ await window.ethereum.enable();
29+ setMetamask(true);
30+ setNetworkType(chainIdToNetworkType(window.ethereum.chainId));
31+ } catch (e) {
32+ console.log('Metamask denied');
24− }
33+ }
25− _setNetworkType(networkType);
34+ }
35+ })();
26− };
36+ }, []);
27
37
28 const { web3, contracts } = useMemo(() => {
38 const { web3, contracts } = useMemo(() => {
29 // given a web3 provider and contract addresses,
39 // given a web3 provider and contract addresses,
30 // build the web3 and contracts objects
40 // build the web3 and contracts objects
31 const initWeb3 = (provider, contractAddresses) => {
41 const initWeb3 = (provider, contractAddresses) => {
42+ // use an in-window eth provider if possible
43+ const _provider = metamask ? window.ethereum : provider;
32− const web3 = new Web3(provider);
44+ const web3 = new Web3(_provider);
33 const contracts = azimuth.initContracts(web3, contractAddresses);
45 const contracts = azimuth.initContracts(web3, contractAddresses);
34
46
35 return {
47 return {
@@ -88,7 +100,7 @@ function _useNetwork(initialNetworkType = null) {
88 };
100 };
89 }
101 }
90 }
102 }
91− }, [networkType]);
103+ }, [networkType, metamask]);
92
104
93 return { networkType, setNetworkType, web3, contracts };
105 return { networkType, setNetworkType, web3, contracts };
94}
106}
src/store/wallet.js+34
@@ -4,6 +4,7 @@ import React, {
4 useCallback,
4 useCallback,
5 useContext,
5 useContext,
6 useState,
6 useState,
7+ useEffect,
7} from 'react';
8} from 'react';
8import { Just, Nothing } from 'folktale/maybe';
9import { Just, Nothing } from 'folktale/maybe';
9import { includes } from 'lodash';
10import { includes } from 'lodash';
@@ -13,9 +14,13 @@ import {
13 DEFAULT_HD_PATH,
14 DEFAULT_HD_PATH,
14 addressFromSecp256k1Public,
15 addressFromSecp256k1Public,
15 walletFromMnemonic,
16 walletFromMnemonic,
17+ NONCUSTODIAL_WALLETS,
16} from 'lib/wallet';
18} from 'lib/wallet';
19+import { getAuthToken } from 'lib/authToken';
17import { BRIDGE_ERROR } from 'lib/error';
20import { BRIDGE_ERROR } from 'lib/error';
18
21
22+import { useNetwork } from 'store/network';
23+
19export const WalletContext = createContext(null);
24export const WalletContext = createContext(null);
20
25
21const DEFAULT_WALLET_TYPE = WALLET_TYPES.TICKET;
26const DEFAULT_WALLET_TYPE = WALLET_TYPES.TICKET;
@@ -46,6 +51,32 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
46 const [networkSeed, setNetworkSeed] = useState(Nothing());
51 const [networkSeed, setNetworkSeed] = useState(Nothing());
47 const [networkRevision, setNetworkRevision] = useState(Nothing());
52 const [networkRevision, setNetworkRevision] = useState(Nothing());
48
53
54+ const [authToken, setAuthToken] = useState(Nothing());
55+
56+ const { web3 } = useNetwork();
57+
58+ useEffect(() => {
59+ (async () => {
60+ if (
61+ !Just.hasInstance(wallet) ||
62+ !Just.hasInstance(web3) ||
63+ NONCUSTODIAL_WALLETS.has(walletType)
64+ ) {
65+ return;
66+ }
67+ const _wallet = wallet.value;
68+ const _web3 = web3.value;
69+ const token = await getAuthToken({
70+ wallet: _wallet,
71+ walletType,
72+ walletHdPath,
73+ web3: _web3,
74+ });
75+
76+ setAuthToken(token);
77+ })();
78+ }, [wallet, walletType, walletHdPath, web3]);
79+
49 const setWalletType = useCallback(
80 const setWalletType = useCallback(
50 walletType => {
81 walletType => {
51 if (!includes(WALLET_TYPES, walletType)) {
82 if (!includes(WALLET_TYPES, walletType)) {
@@ -132,6 +163,9 @@ function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
132 setNetworkRevision,
163 setNetworkRevision,
133 //
164 //
134 resetWallet,
165 resetWallet,
166+ //
167+ authToken,
168+ setAuthToken,
135 };
169 };
136}
170}
137
171
src/views/Login/Ledger.js+17−5
@@ -19,6 +19,7 @@ import {
19import { WALLET_TYPES } from 'lib/wallet';
19import { WALLET_TYPES } from 'lib/wallet';
20import useLoginView from 'lib/useLoginView';
20import useLoginView from 'lib/useLoginView';
21import useBreakpoints from 'lib/useBreakpoints';
21import useBreakpoints from 'lib/useBreakpoints';
22+
22import {
23import {
23 composeValidator,
24 composeValidator,
24 buildCheckboxValidator,
25 buildCheckboxValidator,
@@ -31,6 +32,7 @@ import SubmitButton from 'form/SubmitButton';
31import Condition from 'form/Condition';
32import Condition from 'form/Condition';
32import { FORM_ERROR } from 'final-form';
33import { FORM_ERROR } from 'final-form';
33import { HdPathInput } from 'form/Inputs';
34import { HdPathInput } from 'form/Inputs';
35+import { getAuthToken } from 'lib/authToken';
34
36
35const PATH_OPTIONS = [
37const PATH_OPTIONS = [
36 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
38 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
@@ -45,7 +47,7 @@ const ACCOUNT_OPTIONS = times(20, i => ({
45export default function Ledger({ className, goHome }) {
47export default function Ledger({ className, goHome }) {
46 useLoginView(WALLET_TYPES.LEDGER);
48 useLoginView(WALLET_TYPES.LEDGER);
47
49
48− const { setWallet, setWalletHdPath } = useWallet();
50+ const { setWallet, setWalletHdPath, setAuthToken } = useWallet();
49
51
50 const validate = useMemo(
52 const validate = useMemo(
51 () =>
53 () =>
@@ -70,14 +72,21 @@ export default function Ledger({ className, goHome }) {
70 const chainCode = Buffer.from(info.chainCode, 'hex');
72 const chainCode = Buffer.from(info.chainCode, 'hex');
71 const pub = secp256k1.publicKeyConvert(publicKey, true);
73 const pub = secp256k1.publicKeyConvert(publicKey, true);
72 const hd = bip32.fromPublicKey(pub, chainCode);
74 const hd = bip32.fromPublicKey(pub, chainCode);
75+ const authToken = await getAuthToken({
76+ wallet: hd,
77+ walletType: WALLET_TYPES.LEDGER,
78+ walletHdPath: addHdPrefix(values.hdpath),
79+ });
73
80
81+ setAuthToken(authToken);
74 setWallet(Just(hd));
82 setWallet(Just(hd));
75 setWalletHdPath(addHdPrefix(values.hdpath));
83 setWalletHdPath(addHdPrefix(values.hdpath));
76 } catch (error) {
84 } catch (error) {
85+ console.error(error);
77 return { [FORM_ERROR]: error.message };
86 return { [FORM_ERROR]: error.message };
78 }
87 }
79 },
88 },
80− [setWallet, setWalletHdPath]
89+ [setAuthToken, setWallet, setWalletHdPath]
81 );
90 );
82
91
83 const onValues = useCallback(({ valid, values, form }) => {
92 const onValues = useCallback(({ valid, values, form }) => {
@@ -154,7 +163,9 @@ export default function Ledger({ className, goHome }) {
154 Connect and authenticate to your Ledger, and then open the "Ethereum"
163 Connect and authenticate to your Ledger, and then open the "Ethereum"
155 application. If you're running on older firmware, make sure the "browser
164 application. If you're running on older firmware, make sure the "browser
156 support" option is turned on. To sign transactions, you'll also need to
165 support" option is turned on. To sign transactions, you'll also need to
157− enable the "contract data" option.
166+ enable the "contract data" option. You will be prompted to sign a
167+ message. This allows Bridge to operate correctly. Never sign this
168+ message outside of Bridge.
158 </Grid.Item>
169 </Grid.Item>
159
170
160 <BridgeForm
171 <BridgeForm
@@ -163,7 +174,7 @@ export default function Ledger({ className, goHome }) {
163 onSubmit={onSubmit}
174 onSubmit={onSubmit}
164 afterSubmit={goHome}
175 afterSubmit={goHome}
165 initialValues={initialValues}>
176 initialValues={initialValues}>
166− {({ handleSubmit }) => (
177+ {({ handleSubmit, submitting }) => (
167 <>
178 <>
168 <Condition when="useCustomPath" is={true}>
179 <Condition when="useCustomPath" is={true}>
169 <Grid.Item full as={HdPathInput} name="hdpath" label="HD Path" />
180 <Grid.Item full as={HdPathInput} name="hdpath" label="HD Path" />
@@ -204,7 +215,8 @@ export default function Ledger({ className, goHome }) {
204 <Grid.Item full as={FormError} />
215 <Grid.Item full as={FormError} />
205
216
206 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
217 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
207− Authenticate
218+ {!submitting && 'Authenticate'}
219+ {submitting && 'Please check your device'}
208 </Grid.Item>
220 </Grid.Item>
209 </>
221 </>
210 )}
222 )}
src/views/Login/Metamask.js+28−8
@@ -1,26 +1,46 @@
1import React, { useCallback } from 'react';
1import React, { useCallback } from 'react';
2import { Grid, Text, LinkButton, H5 } from 'indigo-react';
2import { Grid, Text, LinkButton, H5 } from 'indigo-react';
3import { Just } from 'folktale/maybe';
3import { Just } from 'folktale/maybe';
4+import { FORM_ERROR } from 'final-form';
4
5
5import { useWallet } from 'store/wallet';
6import { useWallet } from 'store/wallet';
7+import { useNetwork } from 'store/network';
6
8
7−import { WALLET_TYPES } from 'lib/wallet';
9+import { WALLET_TYPES, abbreviateAddress } from 'lib/wallet';
8import { MetamaskWallet } from 'lib/metamask';
10import { MetamaskWallet } from 'lib/metamask';
11+import { getAuthToken } from 'lib/authToken';
9import useLoginView from 'lib/useLoginView';
12import useLoginView from 'lib/useLoginView';
13+import * as need from 'lib/need';
10
14
11import SubmitButton from 'form/SubmitButton';
15import SubmitButton from 'form/SubmitButton';
12import BridgeForm from 'form/BridgeForm';
16import BridgeForm from 'form/BridgeForm';
13
17
14export default function Metamask({ className, goHome }) {
18export default function Metamask({ className, goHome }) {
15 useLoginView(WALLET_TYPES.METAMASK);
19 useLoginView(WALLET_TYPES.METAMASK);
16− const { setWallet, setWalletType } = useWallet();
20+ const { setWallet, setWalletType, setAuthToken } = useWallet();
21+
22+ const { web3 } = useNetwork();
23+ const _web3 = need.web3(web3);
17
24
18 const onSubmit = useCallback(async () => {
25 const onSubmit = useCallback(async () => {
26+ try {
19− const accounts = await window.ethereum.enable();
27+ const accounts = await window.ethereum.enable();
20− const wallet = new MetamaskWallet(accounts[0]);
28+ const wallet = new MetamaskWallet(accounts[0]);
29+
30+ const authToken = await getAuthToken({
31+ wallet,
32+ walletType: WALLET_TYPES.METAMASK,
33+ web3: _web3,
34+ });
35+
36+ setAuthToken(authToken);
21− setWallet(Just(wallet));
37+ setWallet(Just(wallet));
22− setWalletType(WALLET_TYPES.METAMASK);
38+ setWalletType(WALLET_TYPES.METAMASK);
39+ } catch (e) {
40+ console.error(e);
41+ return { [FORM_ERROR]: e.message };
42+ }
23− }, [setWallet, setWalletType]);
43+ }, [_web3, setAuthToken, setWallet, setWalletType]);
24
44
25 return (
45 return (
26 <Grid className={className}>
46 <Grid className={className}>
@@ -56,7 +76,7 @@ function Login({ onSubmit, goHome }) {
56 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
76 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
57 Login
77 Login
58 {window.ethereum.selectedAddress &&
78 {window.ethereum.selectedAddress &&
59− ' as ' + window.ethereum.selectedAddress}
79+ ' as ' + abbreviateAddress(window.ethereum.selectedAddress)}
60 </Grid.Item>
80 </Grid.Item>
61 )}
81 )}
62 </BridgeForm>
82 </BridgeForm>
src/views/Login/Trezor.js+16−5
@@ -11,6 +11,7 @@ import { useWallet } from 'store/wallet';
11import { TREZOR_PATH } from 'lib/trezor';
11import { TREZOR_PATH } from 'lib/trezor';
12import { WALLET_TYPES } from 'lib/wallet';
12import { WALLET_TYPES } from 'lib/wallet';
13import useLoginView from 'lib/useLoginView';
13import useLoginView from 'lib/useLoginView';
14+import { getAuthToken } from 'lib/authToken';
14
15
15import {
16import {
16 composeValidator,
17 composeValidator,
@@ -35,7 +36,7 @@ const ACCOUNT_OPTIONS = times(20, i => ({
35export default function Trezor({ className, goHome }) {
36export default function Trezor({ className, goHome }) {
36 useLoginView(WALLET_TYPES.TREZOR);
37 useLoginView(WALLET_TYPES.TREZOR);
37
38
38− const { setWallet, setWalletHdPath } = useWallet();
39+ const { setWallet, setWalletHdPath, setAuthToken } = useWallet();
39
40
40 const validate = useMemo(
41 const validate = useMemo(
41 () =>
42 () =>
@@ -67,10 +68,17 @@ export default function Trezor({ className, goHome }) {
67 const pub = secp256k1.publicKeyConvert(publicKey, true);
68 const pub = secp256k1.publicKeyConvert(publicKey, true);
68 const hd = bip32.fromPublicKey(pub, chainCode);
69 const hd = bip32.fromPublicKey(pub, chainCode);
69
70
71+ const authToken = await getAuthToken({
72+ wallet: hd,
73+ walletType: WALLET_TYPES.TREZOR,
74+ walletHdPath: values.hdPath,
75+ });
76+
77+ setAuthToken(authToken);
70 setWallet(Just(hd));
78 setWallet(Just(hd));
71 setWalletHdPath(values.hdPath);
79 setWalletHdPath(values.hdPath);
72 },
80 },
73− [setWallet, setWalletHdPath]
81+ [setAuthToken, setWallet, setWalletHdPath]
74 );
82 );
75
83
76 const onValues = useCallback(({ valid, values, form }) => {
84 const onValues = useCallback(({ valid, values, form }) => {
@@ -96,7 +104,9 @@ export default function Trezor({ className, goHome }) {
96 <Grid className={className}>
104 <Grid className={className}>
97 <Grid.Item full as={Text} className="f6 gray4 mb3">
105 <Grid.Item full as={Text} className="f6 gray4 mb3">
98 Connect and authenticate to your Trezor. If you'd like to use a custom
106 Connect and authenticate to your Trezor. If you'd like to use a custom
99− derivation path, you may enter it below.
107+ derivation path, you may enter it below. Upon login, you will prompted
108+ to sign the message "Bridge Authentication Token". This allows Bridge to
109+ operate correctly. Never sign this message outside of Bridge.
100 </Grid.Item>
110 </Grid.Item>
101
111
102 <BridgeForm
112 <BridgeForm
@@ -105,7 +115,7 @@ export default function Trezor({ className, goHome }) {
105 onSubmit={onSubmit}
115 onSubmit={onSubmit}
106 afterSubmit={goHome}
116 afterSubmit={goHome}
107 initialValues={initialValues}>
117 initialValues={initialValues}>
108− {({ handleSubmit }) => (
118+ {({ handleSubmit, submitting }) => (
109 <>
119 <>
110 <Condition when="useCustomPath" is={true}>
120 <Condition when="useCustomPath" is={true}>
111 <Grid.Item full as={HdPathInput} name="hdPath" label="HD Path" />
121 <Grid.Item full as={HdPathInput} name="hdPath" label="HD Path" />
@@ -133,7 +143,8 @@ export default function Trezor({ className, goHome }) {
133 <Grid.Item full as={FormError} />
143 <Grid.Item full as={FormError} />
134
144
135 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
145 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
136− Authenticate
146+ {!submitting && 'Authenticate'}
147+ {submitting && 'Please check your device'}
137 </Grid.Item>
148 </Grid.Item>
138 </>
149 </>
139 )}
150 )}