on master help sign in
Merge branch 'master' into hosting-rc
eb070bbLiam Fitzgerald
@@ -1,2 +1,2 @@
1−REACT_APP_INFURA_ENDPOINT='b7d2af9f01534031ba773374f766ef65'
1+REACT_APP_INFURA_ENDPOINT='2599df54929b47099bda360958d75aaf'
2
2
package-lock.json+1−1
@@ -1,6 +1,6 @@
1{
1{
2 "name": "bridge",
2 "name": "bridge",
3− "version": "2.3.5",
3+ "version": "2.4.1",
4 "lockfileVersion": 1,
4 "lockfileVersion": 1,
5 "requires": true,
5 "requires": true,
6 "dependencies": {
6 "dependencies": {
package.json+2−2
@@ -1,6 +1,6 @@
1{
1{
2 "name": "bridge",
2 "name": "bridge",
3− "version": "2.3.5",
3+ "version": "2.4.2",
4 "description": "An application for interacting with Azimuth",
4 "description": "An application for interacting with Azimuth",
5 "repository": {
5 "repository": {
6 "type": "git",
6 "type": "git",
@@ -82,7 +82,7 @@
82 "checksum": "cd build && find . -exec shasum -a256 {} ';' > ../checksums.txt && cd ..",
82 "checksum": "cd build && find . -exec shasum -a256 {} ';' > ../checksums.txt && cd ..",
83 "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 "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 ..",
84 "release": "npm install && npm run build && npm run checksum && npm run zip",
84 "release": "npm install && npm run build && npm run checksum && npm run zip",
85− "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 &",
85+ "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 &",
86 "pilot:setup": "npm run pilot:ganache && truffle deploy",
86 "pilot:setup": "npm run pilot:ganache && truffle deploy",
87 "pilot": "HTTPS=true npm-run-all pilot:setup start pilot:cleanup --continue-on-error",
87 "pilot": "HTTPS=true npm-run-all pilot:setup start pilot:cleanup --continue-on-error",
88 "pilot-invites": "WITH_TEST_STATE=INVITES npm run pilot",
88 "pilot-invites": "WITH_TEST_STATE=INVITES npm run pilot",
src/form/EmailChipInput.js+1−1
@@ -41,7 +41,7 @@ const EmailChipInput = ({ className, name, label, disabled }) => {
41 );
41 );
42
42
43 const addToChips = useCallback(() => {
43 const addToChips = useCallback(() => {
44− fields.push(value);
44+ fields.concat(value.split(/[\n\t, ]+/));
45 setValue('');
45 setValue('');
46 }, [value, fields, setValue]);
46 }, [value, fields, setValue]);
47
47
src/index.js−32
@@ -7,36 +7,4 @@ if (process.env.NODE_ENV !== 'production') {
7 require('@welldone-software/why-did-you-render')(React);
7 require('@welldone-software/why-did-you-render')(React);
8}
8}
9
9
10−if (process.env.NODE_ENV === 'production') {
11− console.log(`
12− MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
13− MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
14− MMMMMMMMMMMMMMMMMMWN0kdlc:::::clox0XWMMMMMMMMMMMMMMMMM
15− MMMMMMMMMMMMMMMN0o;.. .;oONMMMMMMMMMMMMMM
16− MMMMMMMMMMMMMKd,. .':ldkOOOOOkdo:'. 'oKWMMMMMMMMMMM
17− MMMMMMMMMMMXo. .;d0NMMMMMMMMMMMMMN0d;. .lKMMMMMMMMMM
18− MMMMMMMMMWk' .:OWMMMMMMMMMMMMMMMMMMMW0l. .xWMMMMMMMM
19− MMMMMMMMWd. ,OWMMMMMMMMMMMMMMMMMMMMMMMW0; .oNMMMMMMM
20− MMMMMMMWx. ;KMMMMMMMMMMMMMMMMMMMMMMMMMMMXc .oWMMMMMM
21− MMMMMMM0' ,KMMMMMMMMMWNWMMMMMMMMMMMMMMMMMX: .OMMMMMM
22− MMMMMMWo .xMMMMMMWOl;'',:oxONMMWOc:clOWMMMk. lWMMMMM
23− MMMMMMN: '0MMMMMK: ,loc. .dWMMMK, ;XMMMMM
24− MMMMMMN: ,KMMMMK; ... .lNMMMMX; ,KMMMMM
25− MMMMMMWc .OMMMMx. ,OXKkc;' .;kNMMMMM0' :NMMMMM
26− MMMMMMMx. lNMMMN0OOOXMMMMMWN0xddkKWMMMMMMWo dMMMMMM
27− MMMMMMMNc .xWMMMMMMMMMMMMMMMMMMMMMMMMMMMWk. ;XMMMMMM
28− MMMMMMMMK; .dNMMMMMMMMMMMMMMMMMMMMMMMMMWx. ,0MMMMMMM
29− MMMMMMMMMK: :0WMMMMMMMMMMMMMMMMMMMMMW0c. ;0MMMMMMMM
30− MMMMMMMMMMNd. .:kNMMMMMMMMMMMMMMMMMNOc. .oXMMMMMMMMM
31− MMMMMMMMMMMMKo. 'cdOKNWMMMMMWNXOxc' .l0WMMMMMMMMMM
32− MMMMMMMMMMMMMMXx:. .',;:::;,'. .:xXMMMMMMMMMMMMM
33− MMMMMMMMMMMMMMMMMXOoc,'.. ...,:okXWMMMMMMMMMMMMMMM
34− MMMMMMMMMMMMMMMMMMMMMWNK00OOO00KNWMMMMMMMMMMMMMMMMMMMM
35− MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
36− MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
37−
38− Like Urbit? Want to work with us? Try apply@tlon.io.
39− `);
40−}
41−
42ReactDOM.render(<Bridge />, document.getElementById('root'));
10ReactDOM.render(<Bridge />, document.getElementById('root'));
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/constants.js+3−3
@@ -1,7 +1,7 @@
1import { isDevelopment } from './flags';
1import { isDevelopment } from './flags';
2
2
3−const CHECK_BLOCK_EVERY_MS = isDevelopment ? 1000 : 5000;
3+const CHECK_BLOCK_EVERY_MS = isDevelopment ? 1000 : 10000;
4−const DEFAULT_GAS_PRICE_GWEI = 20;
4+const DEFAULT_GAS_PRICE_GWEI = 40;
5
5
6const MIN_GALAXY = 0;
6const MIN_GALAXY = 0;
7const MAX_GALAXY = 255;
7const MAX_GALAXY = 255;
@@ -31,7 +31,7 @@ const GAS_LIMITS = {
31 REJECT: 200000, //NOTE no samples
31 REJECT: 200000, //NOTE no samples
32 DETACH: 200000, //NOTE no samples
32 DETACH: 200000, //NOTE no samples
33 //
33 //
34− GIFT_PLANET: 420000, //NOTE low sample size, //NOTE also update in gas tank
34+ GIFT_PLANET: 450000, //NOTE low sample size, //NOTE also update in gas tank
35 //
35 //
36 SEND_ETH: 21000,
36 SEND_ETH: 21000,
37 //
37 //
src/lib/getSuggestedGasPrice.js+1−1
@@ -24,7 +24,7 @@ export default async function getSuggestedGasPrice(networkType) {
24 // more than gwei. see: https://docs.ethgasstation.info
24 // more than gwei. see: https://docs.ethgasstation.info
25 const suggestedGasPrice = Math.ceil(json.fast / 10); // to gwei
25 const suggestedGasPrice = Math.ceil(json.fast / 10); // to gwei
26
26
27− // we don't want to charge users more than our default 20 gwei
27+ // we don't want to charge users more than our default gwei
28 return Math.min(suggestedGasPrice, DEFAULT_GAS_PRICE_GWEI);
28 return Math.min(suggestedGasPrice, DEFAULT_GAS_PRICE_GWEI);
29 } catch (e) {
29 } catch (e) {
30 return DEFAULT_GAS_PRICE_GWEI;
30 return DEFAULT_GAS_PRICE_GWEI;
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/Activate/ActivateCode.js+20−16
@@ -157,7 +157,7 @@ export default function ActivateCode() {
157 </Grid.Item>
157 </Grid.Item>
158 <Grid.Item full as={P} className="mb2">
158 <Grid.Item full as={P} className="mb2">
159 Someone has invited you to claim your Urbit identity and join the
159 Someone has invited you to claim your Urbit identity and join the
160− network. Enter your activation code to continue.
160+ network. {!impliedTicket && 'Enter your activation code to continue.'}
161 </Grid.Item>
161 </Grid.Item>
162 <BridgeForm
162 <BridgeForm
163 validate={validate}
163 validate={validate}
@@ -166,21 +166,25 @@ export default function ActivateCode() {
166 initialValues={initialValues}>
166 initialValues={initialValues}>
167 {({ validating, values, submitting, handleSubmit }) => (
167 {({ validating, values, submitting, handleSubmit }) => (
168 <>
168 <>
169+ {!impliedTicket && (
170+ <>
169− <Grid.Item
171+ <Grid.Item
170− full
172+ full
171− as={TicketInput}
173+ as={TicketInput}
172− type={values.showTicket ? 'text' : 'password'}
174+ type={values.showTicket ? 'text' : 'password'}
173− name="ticket"
175+ name="ticket"
174− label="Activation Code"
176+ label="Activation Code"
175− disabled={!activationAllowed}
177+ disabled={!activationAllowed}
176− />
178+ />
177−
179+
178− <Grid.Item
180+ <Grid.Item
179− full
181+ full
180− as={CheckboxInput}
182+ as={CheckboxInput}
181− name="showTicket"
183+ name="showTicket"
182− label="Show"
184+ label="Show"
183− />
185+ />
186+ </>
187+ )}
184
188
185 <Grid.Item full as={FormError} />
189 <Grid.Item full as={FormError} />
186
190
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 )}
src/views/UrbitOS/NetworkingKeys.js+1−1
@@ -246,7 +246,7 @@ export default function UrbitOSNetworkingKeys({
246 <Condition when="useNetworkSeed" is={true}>
246 <Condition when="useNetworkSeed" is={true}>
247 <Grid.Item full as={NoticeBox} className="mb2">
247 <Grid.Item full as={NoticeBox} className="mb2">
248 When using a custom network seed, you'll need to download
248 When using a custom network seed, you'll need to download
249− your Arvo keyfile immediately after this transaction is
249+ your Arvo keyfile immediately after this transaction as
250 Bridge does not store your seed.
250 Bridge does not store your seed.
251 </Grid.Item>
251 </Grid.Item>
252 <Grid.Item
252 <Grid.Item