on master help sign in
Merge pull request #24 from urbit/jt-alternate-auth

Additional authentication methods

commit8689d117e63275057e50b0e14da5344183d95918 authorWill Kim <william.jahaziel.kim@gmail.com> dateThu 31 Jan 2019, 12:40 (8 years ago) parentcf910803c1cd58
26 files changed+1036−682
package-lock.json+44−4 package.json+2−1 src/Bridge.js+19−1 src/components/Base.js+2 src/components/UploadButton.js+29 src/lib/error.js+1 src/lib/router.js+16−2 src/lib/wallet.js+16−2 src/style/extend.css+4 src/views/AcceptTransfer.js+1−4 src/views/CancelTransfer.js+46−48 src/views/CreateGalaxy.js+84−87 src/views/GenKeyfile.js+23−25 src/views/IssueChild.js+82−85 src/views/Keystore.js+168 src/views/Ledger.js+31−33 src/views/Mnemonic.js+1−3 src/views/PrivateKey.js+85 src/views/SetKeys.js+54−58 src/views/SetProxy.js+66−68 src/views/Shards.js+77−80 src/views/Ticket.js+50−53 src/views/Transfer.js+52−55 src/views/Trezor.js+31−34 src/views/ViewPoint.js+29−31 src/views/Wallet.js+23−8
package-lock.json+44−4
@@ -1,6 +1,6 @@
11{
22 "name": "bridge",
3− "version": "0.4.0",
3+ "version": "1.0.1",
44 "lockfileVersion": 1,
55 "requires": true,
66 "dependencies": {
@@ -1965,9 +1965,9 @@
19651965 }
19661966 },
19671967 "azimuth-js": {
1968− "version": "0.8.3",
1969− "resolved": "https://registry.npmjs.org/azimuth-js/-/azimuth-js-0.8.3.tgz",
1970− "integrity": "sha512-ls668YS6pVwplapzqAk+wfDOLr+DW9QEwN1PwVgsxXqgls1b1pYqqjmREbWHexbTSCbSW31h8JHnUIc6sOf7mQ==",
1968+ "version": "0.10.0",
1969+ "resolved": "https://registry.npmjs.org/azimuth-js/-/azimuth-js-0.10.0.tgz",
1970+ "integrity": "sha512-VDg60BYSpTjsv1K/DX7VY5F0HbxHV0X1cSoG3c1iZYH5Lxy8TErYq4CJ+D6er0xtI7JI3hmKOZS+p+ru42sT3A==",
19711971 "requires": {
19721972 "azimuth-solidity": "1.0.2",
19731973 "ethereumjs-util": "^5.2.0",
@@ -10117,6 +10117,41 @@
1011710117 "sha3": "^1.2.2"
1011810118 }
1011910119 },
10120+ "keythereum": {
10121+ "version": "1.0.4",
10122+ "resolved": "https://registry.npmjs.org/keythereum/-/keythereum-1.0.4.tgz",
10123+ "integrity": "sha512-c3gWM0nQ6x5TKAzTOA1yIqn73S8sP9+lR7mc7QS6t509g7C0/CukykxGA6+B+aXI6BIrlSwVh5muPv/I1lD9LA==",
10124+ "requires": {
10125+ "crypto-browserify": "3.12.0",
10126+ "keccak": "1.4.0",
10127+ "scrypt": "6.0.3",
10128+ "secp256k1": "3.5.0",
10129+ "sjcl": "1.0.6",
10130+ "uuid": "3.0.0"
10131+ },
10132+ "dependencies": {
10133+ "secp256k1": {
10134+ "version": "3.5.0",
10135+ "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.5.0.tgz",
10136+ "integrity": "sha512-e5QIJl8W7Y4tT6LHffVcZAxJjvpgE5Owawv6/XCYPQljE9aP2NFFddQ8OYMKhdLshNu88FfL3qCN3/xYkXGRsA==",
10137+ "requires": {
10138+ "bindings": "^1.2.1",
10139+ "bip66": "^1.1.3",
10140+ "bn.js": "^4.11.3",
10141+ "create-hash": "^1.1.2",
10142+ "drbg.js": "^1.0.1",
10143+ "elliptic": "^6.2.3",
10144+ "nan": "^2.2.1",
10145+ "safe-buffer": "^5.1.0"
10146+ }
10147+ },
10148+ "uuid": {
10149+ "version": "3.0.0",
10150+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz",
10151+ "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg="
10152+ }
10153+ }
10154+ },
1012010155 "killable": {
1012110156 "version": "1.0.1",
1012210157 "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -17622,6 +17657,11 @@
1762217657 "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz",
1762317658 "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g=="
1762417659 },
17660+ "sjcl": {
17661+ "version": "1.0.6",
17662+ "resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.6.tgz",
17663+ "integrity": "sha1-ZBVGKmPMDUIVxJuuydP6DBtTUg8="
17664+ },
1762517665 "slash": {
1762617666 "version": "1.0.0",
1762717667 "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
package.json+2−1
@@ -16,7 +16,7 @@
1616 "dependencies": {
1717 "@ledgerhq/hw-app-eth": "^4.24.0",
1818 "@ledgerhq/hw-transport-u2f": "^4.28.0",
19− "azimuth-js": "0.8.3",
19+ "azimuth-js": "^0.10.0",
2020 "azimuth-solidity": "1.0.2",
2121 "babel-polyfill": "^6.26.0",
2222 "bip32": "^1.0.2",
@@ -29,6 +29,7 @@
2929 "folktale": "^2.3.1",
3030 "immutable": "^4.0.0-rc.12",
3131 "keccak": "^1.4.0",
32+ "keythereum": "^1.0.4",
3233 "lodash": "^4.17.11",
3334 "prop-types": "^15.6.2",
3435 "react": "^16.6.0",
src/Bridge.js+19−1
@@ -17,11 +17,29 @@ class Bridge extends React.Component {
1717 constructor(props) {
1818 super(props)
1919
20+ const networkType =
21+ process.env.NODE_ENV === 'development'
22+ ? NETWORK_NAMES.LOCAL
23+ : NETWORK_NAMES.MAINNET
24+
25+ // NB (jtobin):
26+ //
27+ // Note that the 'wallet' prop has type depending on the 'walletType' prop.
28+ // These could be bound together in a single structure (so that
29+ // 'walletType' acted more explicitly as a data constructor of sorts) but
30+ // it doesn't necessarily help us much, since we branch on 'walletType'
31+ // before setting 'wallet'.
32+ //
33+ // Wallets are always wrapped in Maybe. For most authentication
34+ // mechanisms, Maybe wraps a BIP32 HD wallet; for Ethereum private keys,
35+ // JSON keystore files, and Metamask authentication, it wraps an
36+ // 'EthereumWallet'.
37+
2038 this.state = {
2139 // routes
2240 routeCrumbs: Stack([ ROUTE_NAMES.LANDING ]),
2341 // network
24− networkType: NETWORK_NAMES.MAINNET,
42+ networkType: networkType,
2543 web3: Maybe.Nothing(),
2644 contracts: Maybe.Nothing(),
2745 // wallet
src/components/Base.js+2
@@ -17,6 +17,7 @@ import {
1717
1818// Buttons
1919import Button from './Button';
20+import UploadButton from './UploadButton';
2021// import Checkbox from './Checkbox';
2122import CheckboxButton from './CheckboxButton';
2223import Anchor from './Anchor';
@@ -153,6 +154,7 @@ const ShardInput = advancedInput({
153154export {
154155 Button,
155156 CheckboxButton,
157+ UploadButton,
156158 Anchor,
157159 InputCaption,
158160
src/components/UploadButton.jsnew file+29
@@ -0,0 +1,29 @@
1+import React from 'react';
2+
3+class UploadButton extends React.Component {
4+ constructor(props) {
5+ super(props);
6+ this.onChange = this.onChange.bind(this);
7+ }
8+
9+ onChange() {
10+ this.props.onChange(this.input);
11+ }
12+
13+ render() {
14+ return (
15+ <label className={this.props.className}>
16+ <input
17+ className={ 'hidden h-0 w-0 abs' }
18+ ref={(ref) => { this.input = ref; }}
19+ onChange={this.onChange}
20+ disabled={this.props.disabled}
21+ type="file"
22+ />
23+ {this.props.children}
24+ </label>
25+ );
26+ }
27+}
28+
29+export default UploadButton;
src/lib/error.js+1
@@ -4,6 +4,7 @@ const BRIDGE_ERROR = {
44 MISSING_WALLET: new Error('no wallet found'),
55 MISSING_TXN: new Error('no transaction found'),
66 MISSING_CONTRACTS: new Error('no contracts found'),
7+ MISSING_KEYSTORE: new Error('no keystore text found'),
78 MISSING_MNEMONIC: new Error('no authentication mnemonic found'),
89 MISSING_URBIT_WALLET: new Error('no urbit wallet found'),
910 MISSING_POINT: new Error('no point found'),
src/lib/router.js+16−2
@@ -10,6 +10,8 @@ import Network from '../views/Network'
1010import Mnemonic from '../views/Mnemonic'
1111import Ledger from '../views/Ledger'
1212import Trezor from '../views/Trezor'
13+import PrivateKey from '../views/PrivateKey'
14+import Keystore from '../views/Keystore'
1315import ViewPoint from '../views/ViewPoint'
1416import SentTransaction from '../views/SentTransaction'
1517import SetKeys from '../views/SetKeys'
@@ -25,7 +27,7 @@ import Ticket from '../views/Ticket'
2527import Transfer from '../views/Transfer'
2628import Wallet from '../views/Wallet'
2729
28−import { addressFromSecp256k1Public } from './wallet'
30+import { addressFromSecp256k1Public, EthereumWallet } from './wallet'
2931import { renderNetworkType } from './network'
3032import { BRIDGE_ERROR } from './error'
3133
@@ -40,6 +42,8 @@ const ROUTE_NAMES = {
4042 SHARDS: Symbol('SHARDS'),
4143 LEDGER: Symbol('LEDGER'),
4244 TREZOR: Symbol('TREZOR'),
45+ PRIVATE_KEY: Symbol('PRIVATE_KEY'),
46+ KEYSTORE: Symbol('KEYSTORE'),
4347 VIEW_SHIP: Symbol('VIEW_SHIP'),
4448 SHIPS: Symbol('SHIPS'),
4549 SHIP: Symbol('SHIP'),
@@ -68,6 +72,8 @@ const createRoutes = () => {
6872 routes[ROUTE_NAMES.SHARDS] = Shards
6973 routes[ROUTE_NAMES.LEDGER] = Ledger
7074 routes[ROUTE_NAMES.TREZOR] = Trezor
75+ routes[ROUTE_NAMES.PRIVATE_KEY] = PrivateKey
76+ routes[ROUTE_NAMES.KEYSTORE] = Keystore
7177 routes[ROUTE_NAMES.SHIPS] = Points
7278 routes[ROUTE_NAMES.SHIP] = Point
7379 routes[ROUTE_NAMES.SET_MANAGEMENT_PROXY] = SetManagementProxy
@@ -98,7 +104,9 @@ const renderRoute = (props, route) => {
98104 ? wallet.matchWith({
99105 Nothing: () => 'Wallet',
100106 Just: (wal) =>
101− addressFromSecp256k1Public(wal.value.publicKey)
107+ wal.value instanceof EthereumWallet
108+ ? wal.value.address
109+ : addressFromSecp256k1Public(wal.value.publicKey)
102110 })
103111
104112 : route === ROUTE_NAMES.MNEMONIC
@@ -116,6 +124,12 @@ const renderRoute = (props, route) => {
116124 : route === ROUTE_NAMES.TREZOR
117125 ? 'Trezor'
118126
127+ : route === ROUTE_NAMES.PRIVATE_KEY
128+ ? 'Private Key'
129+
130+ : route === ROUTE_NAMES.KEYSTORE
131+ ? 'Keystore File'
132+
119133 : route === ROUTE_NAMES.SHIPS
120134 ? 'Points'
121135
src/lib/wallet.js+16−2
@@ -15,7 +15,16 @@ const WALLET_NAMES = {
1515 TICKET: Symbol('TICKET'),
1616 SHARDS: Symbol('SHARDS'),
1717 LEDGER: Symbol('LEDGER'),
18− TREZOR: Symbol('TREZOR')
18+ TREZOR: Symbol('TREZOR'),
19+ PRIVATE_KEY: Symbol('PRIVATE_KEY'),
20+ KEYSTORE: Symbol('KEYSTORE')
21+}
22+
23+function EthereumWallet(privateKey) {
24+ this.privateKey = privateKey
25+ this.publicKey = secp256k1.publicKeyCreate(this.privateKey)
26+ const pub = this.publicKey.toString('hex')
27+ this.address = addressFromSecp256k1Public(pub)
1928}
2029
2130const renderWalletType = (wallet) =>
@@ -29,6 +38,10 @@ const renderWalletType = (wallet) =>
2938 ? 'Ledger'
3039 : wallet === WALLET_NAMES.TREZOR
3140 ? 'Trezor'
41+ : wallet === WALLET_NAMES.PRIVATE_KEY
42+ ? 'Private Key'
43+ : wallet === WALLET_NAMES.KEYSTORE
44+ ? 'Keystore File'
3245 : 'Wallet'
3346
3447const addressFromSecp256k1Public = pub => {
@@ -105,5 +118,6 @@ export {
105118 addressFromSecp256k1Public,
106119 eqAddr,
107120 walletFromMnemonic,
108− addHexPrefix
121+ addHexPrefix,
122+ EthereumWallet
109123}
src/style/extend.css+4
@@ -435,3 +435,7 @@ footer,
435435code {
436436 padding: 2px;
437437}
438+
439+.pointer:hover {
440+ cursor: pointer;
441+}
src/views/AcceptTransfer.js+1−4
@@ -1,6 +1,6 @@
11import Maybe from 'folktale/maybe'
22import React from 'react'
3−import { Row, Col, H1, H3, P, Form, Warning } from '../components/Base'
3+import { Row, Col, H1, H3, P, Warning } from '../components/Base'
44import { InnerLabel, AddressInput, ShowBlockie, Anchor } from '../components/Base'
55import * as azimuth from 'azimuth-js'
66import * as ob from 'urbit-ob'
@@ -277,7 +277,6 @@ class AcceptTransfer extends React.Component {
277277 "as. But you can transfer to any address you like."
278278 }
279279 </P>
280− <Form>
281280 <AddressInput
282281 className='mono mt-8'
283282 prop-size='lg'
@@ -329,8 +328,6 @@ class AcceptTransfer extends React.Component {
329328 setGasLimit={this.handleSetGasLimit}
330329 handleSubmit={this.handleSubmit} />
331330
332− </Form>
333−
334331 {
335332 Maybe.Nothing.hasInstance(state.txError)
336333 ? ''
src/views/CancelTransfer.js+46−48
@@ -1,6 +1,6 @@
11import Maybe from 'folktale/maybe'
22import React from 'react'
3−import { Row, Col, H1, P, Form, Warning, H3 } from '../components/Base'
3+import { Row, Col, H1, P, Warning, H3 } from '../components/Base'
44import * as azimuth from 'azimuth-js'
55import * as ob from 'urbit-ob'
66
@@ -227,53 +227,51 @@ class CancelTransfer extends React.Component {
227227 `This action will cancel the transfer to ${proxy}.`
228228 }
229229 </P>
230− <Form>
231− <StatelessTransaction
232− // Upper scope
233− web3={props.web3}
234− contracts={props.contracts}
235− wallet={props.wallet}
236− walletType={props.walletType}
237− walletHdPath={props.walletHdPath}
238− // Tx
239− txn={state.txn}
240− stx={state.stx}
241− // Tx details
242− nonce={state.nonce}
243− gasPrice={state.gasPrice}
244− chainId={state.chainId}
245− gasLimit={state.gasLimit}
246− // Checks
247− userApproval={state.userApproval}
248− canGenerate={ canGenerate }
249− canSign={ canSign }
250− canApprove={ canApprove }
251− canSend={ canSend }
252− // Methods
253− createUnsignedTxn={this.handleCreateUnsignedTxn}
254− setUserApproval={this.handleSetUserApproval}
255− setTxn={this.handleSetTxn}
256− setStx={this.handleSetStx}
257− setNonce={this.handleSetNonce}
258− setChainId={this.handleSetChainId}
259− setGasPrice={this.handleSetGasPrice}
260− setGasLimit={this.handleSetGasLimit}
261− handleSubmit={this.handleSubmit} />
262−
263− {
264− Maybe.Nothing.hasInstance(state.txError)
265− ? ''
266− : <Warning className={'mt-8'}>
267− <H3 style={{marginTop: 0, paddingTop: 0}}>
268− {
269− 'There was an error sending your transaction.'
270− }
271− </H3>
272− { state.txError.value }
273− </Warning>
274− }
275−
276− </Form>
230+ <StatelessTransaction
231+ // Upper scope
232+ web3={props.web3}
233+ contracts={props.contracts}
234+ wallet={props.wallet}
235+ walletType={props.walletType}
236+ walletHdPath={props.walletHdPath}
237+ // Tx
238+ txn={state.txn}
239+ stx={state.stx}
240+ // Tx details
241+ nonce={state.nonce}
242+ gasPrice={state.gasPrice}
243+ chainId={state.chainId}
244+ gasLimit={state.gasLimit}
245+ // Checks
246+ userApproval={state.userApproval}
247+ canGenerate={ canGenerate }
248+ canSign={ canSign }
249+ canApprove={ canApprove }
250+ canSend={ canSend }
251+ // Methods
252+ createUnsignedTxn={this.handleCreateUnsignedTxn}
253+ setUserApproval={this.handleSetUserApproval}
254+ setTxn={this.handleSetTxn}
255+ setStx={this.handleSetStx}
256+ setNonce={this.handleSetNonce}
257+ setChainId={this.handleSetChainId}
258+ setGasPrice={this.handleSetGasPrice}
259+ setGasLimit={this.handleSetGasLimit}
260+ handleSubmit={this.handleSubmit} />
261+
262+ {
263+ Maybe.Nothing.hasInstance(state.txError)
264+ ? ''
265+ : <Warning className={'mt-8'}>
266+ <H3 style={{marginTop: 0, paddingTop: 0}}>
267+ {
268+ 'There was an error sending your transaction.'
269+ }
270+ </H3>
271+ { state.txError.value }
272+ </Warning>
273+ }
274+
277275 </Col>
278276 </Row>
279277 )
src/views/CreateGalaxy.js+84−87
@@ -5,7 +5,7 @@ import * as ob from 'urbit-ob'
55
66import { Button, } from '../components/Base'
77import { Row, Col, H1, P, Anchor } from '../components/Base'
8−import { InnerLabel, GalaxyInput, AddressInput, ValidatedSigil, ShowBlockie, Form } from '../components/Base'
8+import { InnerLabel, GalaxyInput, AddressInput, ValidatedSigil, ShowBlockie } from '../components/Base'
99import StatelessTransaction from '../components/StatelessTransaction'
1010
1111import { BRIDGE_ERROR } from '../lib/error'
@@ -192,93 +192,90 @@ class CreateGalaxy extends React.Component {
192192 }
193193 </P>
194194
195− <Form>
196−
197− <GalaxyInput
198− className='mono'
199− prop-size='lg'
200− prop-format='innerLabel'
201− autoFocus
202− placeholder='e.g. ~zod'
203− value={state.galaxyName}
204− onChange={v => this.handleGalaxyNameInput(v)}>
205− <InnerLabel>{ 'Galaxy Name' }</InnerLabel>
206− <ValidatedSigil
207− className='tr-0 mt-05 mr-0 abs'
208− patp={state.galaxyName}
209− size={76}
210− margin={8} />
211− </GalaxyInput>
212−
213− <AddressInput
214− className='mono mt-8'
215− prop-size='lg'
216− prop-format='innerLabel'
217− value={ state.galaxyOwner }
218− onChange={ v => this.handleAddressInput(v) }>
219− <InnerLabel>
220− { 'Address that will own this galaxy' }
221− </InnerLabel>
222− <ShowBlockie
223− className={'mt-1'}
224− address={state.galaxyOwner} />
225− </AddressInput>
226−
227− <Anchor
195+ <GalaxyInput
196+ className='mono'
197+ prop-size='lg'
198+ prop-format='innerLabel'
199+ autoFocus
200+ placeholder='e.g. ~zod'
201+ value={state.galaxyName}
202+ onChange={v => this.handleGalaxyNameInput(v)}>
203+ <InnerLabel>{ 'Galaxy Name' }</InnerLabel>
204+ <ValidatedSigil
205+ className='tr-0 mt-05 mr-0 abs'
206+ patp={state.galaxyName}
207+ size={76}
208+ margin={8} />
209+ </GalaxyInput>
210+
211+ <AddressInput
212+ className='mono mt-8'
213+ prop-size='lg'
214+ prop-format='innerLabel'
215+ value={ state.galaxyOwner }
216+ onChange={ v => this.handleAddressInput(v) }>
217+ <InnerLabel>
218+ { 'Address that will own this galaxy' }
219+ </InnerLabel>
220+ <ShowBlockie
228221 className={'mt-1'}
229− prop-size={'sm'}
230− prop-disabled={!validAddress}
231− target={'_blank'}
232− href={`https://etherscan.io/address/${state.galaxyOwner}`}>
233− {'View on Etherscan ↗'}
234− </Anchor>
235−
236− <Button
237− prop-size='lg wide'
238− className='mt-8'
239− prop-color={buttonTriState(state.isAvailable)}
240− disabled={!validGalaxy}
241− onClick={() => this.confirmAvailability()}>
242− {buttonTriStateText(state.isAvailable)}
243− </Button>
244−
245− <StatelessTransaction
246− address={state.galaxyOwner}
247− shipName={state.galaxyName}
248− isAvailable={state.isAvailable}
249− web3={props.web3}
250− contracts={props.contracts}
251− wallet={props.wallet}
252− walletType={props.walletType}
253− walletHdPath={props.walletHdPath}
254− // Tx
255− txApproval={state.txApproval}
256− txn={state.txn}
257− stx={state.stx}
258− nonce={state.nonce}
259− gasPrice={state.gasPrice}
260− chainId={state.chainId}
261− gasLimit={state.gasLimit}
262− canGenerate={
263− validAddress === true &&
264− validGalaxy === true &&
265− state.isAvailable === true
266− }
267− canSign={!Maybe.Nothing.hasInstance(state.txn)}
268− canSend={!Maybe.Nothing.hasInstance(state.stx)}
269− // Methods
270− createUnsignedTxn={() => this.createUnsignedTxn()}
271− clearTransaction={() => this.clearTransaction()}
272− setApproval={() => this.setState({txApproval: !state.txApproval})}
273− setTxn={txn => this.setState({txn})}
274− setStx={stx => this.setState({stx})}
275− setNonce={nonce => this.setState({nonce})}
276− setChainId={chainId => this.setState({chainId})}
277− setGasPrice={gasPrice => this.setState({gasPrice})}
278− setGasLimit={gasLimit => this.setState({gasLimit})}
279− handleSubmit={this.handleSubmit} />
280−
281− </Form>
222+ address={state.galaxyOwner} />
223+ </AddressInput>
224+
225+ <Anchor
226+ className={'mt-1'}
227+ prop-size={'sm'}
228+ prop-disabled={!validAddress}
229+ target={'_blank'}
230+ href={`https://etherscan.io/address/${state.galaxyOwner}`}>
231+ {'View on Etherscan ↗'}
232+ </Anchor>
233+
234+ <Button
235+ prop-size='lg wide'
236+ className='mt-8'
237+ prop-color={buttonTriState(state.isAvailable)}
238+ disabled={!validGalaxy}
239+ onClick={() => this.confirmAvailability()}>
240+ {buttonTriStateText(state.isAvailable)}
241+ </Button>
242+
243+ <StatelessTransaction
244+ address={state.galaxyOwner}
245+ shipName={state.galaxyName}
246+ isAvailable={state.isAvailable}
247+ web3={props.web3}
248+ contracts={props.contracts}
249+ wallet={props.wallet}
250+ walletType={props.walletType}
251+ walletHdPath={props.walletHdPath}
252+ // Tx
253+ txApproval={state.txApproval}
254+ txn={state.txn}
255+ stx={state.stx}
256+ nonce={state.nonce}
257+ gasPrice={state.gasPrice}
258+ chainId={state.chainId}
259+ gasLimit={state.gasLimit}
260+ canGenerate={
261+ validAddress === true &&
262+ validGalaxy === true &&
263+ state.isAvailable === true
264+ }
265+ canSign={!Maybe.Nothing.hasInstance(state.txn)}
266+ canSend={!Maybe.Nothing.hasInstance(state.stx)}
267+ // Methods
268+ createUnsignedTxn={() => this.createUnsignedTxn()}
269+ clearTransaction={() => this.clearTransaction()}
270+ setApproval={() => this.setState({txApproval: !state.txApproval})}
271+ setTxn={txn => this.setState({txn})}
272+ setStx={stx => this.setState({stx})}
273+ setNonce={nonce => this.setState({nonce})}
274+ setChainId={chainId => this.setState({chainId})}
275+ setGasPrice={gasPrice => this.setState({gasPrice})}
276+ setGasLimit={gasLimit => this.setState({gasLimit})}
277+ handleSubmit={this.handleSubmit} />
278+
282279 </Col>
283280 </Row>
284281 )
src/views/GenKeyfile.js+23−25
@@ -3,7 +3,7 @@ import React from 'react'
33import { Button } from '../components/Base'
44import { RequiredInput, InnerLabel } from '../components/Base'
55import { Row, Col, H1, P } from '../components/Base'
6−import { Form, Code } from '../components/Base'
6+import { Code } from '../components/Base'
77
88import * as ob from 'urbit-ob'
99import * as kg from '../../node_modules/urbit-key-generation/dist/index'
@@ -124,32 +124,30 @@ class GenKeyfile extends React.Component {
124124 }
125125 </P>
126126
127− <Form>
128− <RequiredInput
129− className='mono'
130− prop-size='lg'
131− prop-format='innerLabel'
132− autoFocus
133− value={ networkSeed }
134− onChange={ this.handleNetworkSeedInput }>
135− <InnerLabel>{ 'Network seed' }</InnerLabel>
136− </RequiredInput>
137−
138− <Button
139− className={'mt-8'}
140− color={'blue'}
141− onClick={
142− () => {
143− if (hexRegExp.test(networkSeed)) {
144− const keyfile = genKey(networkSeed, point, revision)
145− this.setState({ keyfile })
146− }
127+ <RequiredInput
128+ className='mono'
129+ prop-size='lg'
130+ prop-format='innerLabel'
131+ autoFocus
132+ value={ networkSeed }
133+ onChange={ this.handleNetworkSeedInput }>
134+ <InnerLabel>{ 'Network seed' }</InnerLabel>
135+ </RequiredInput>
136+
137+ <Button
138+ className={'mt-8'}
139+ color={'blue'}
140+ onClick={
141+ () => {
142+ if (hexRegExp.test(networkSeed)) {
143+ const keyfile = genKey(networkSeed, point, revision)
144+ this.setState({ keyfile })
147145 }
148146 }
149− >
150− { 'Generate →' }
151− </Button>
152− </Form>
147+ }
148+ >
149+ { 'Generate →' }
150+ </Button>
153151
154152 <Code>
155153 { keyfile }
src/views/IssueChild.js+82−85
@@ -5,7 +5,7 @@ import * as ob from 'urbit-ob'
55
66import { Row, Col, H1, H3, P, Anchor, Warning } from '../components/Base'
77import { Button, ShowBlockie, ValidatedSigil } from '../components/Base'
8−import { PointInput, AddressInput, Form, InnerLabel } from '../components/Base'
8+import { PointInput, AddressInput, InnerLabel } from '../components/Base'
99
1010import StatelessTransaction from '../components/StatelessTransaction'
1111
@@ -402,92 +402,89 @@ class IssueChild extends React.Component {
402402 { '.' }
403403 </P>
404404
405− <Form>
406− <PointInput
407− prop-size='lg'
408− prop-format='innerLabel'
409− className={'mono mt-8'}
410− placeholder={ `e.g. ${state.suggestions[3]}` }
411− value={ state.desiredPoint }
412− onChange={ this.handlePointInput }>
413− <InnerLabel>{ 'Point to Issue' }</InnerLabel>
414− <ValidatedSigil
415− className={'tr-0 mt-05 mr-0 abs'}
416− patp={state.desiredPoint}
417− size={76}
418− margin={8}
419− validator={() => this.validatePoint(state.desiredPoint)} />
420− </PointInput>
421−
422− <AddressInput
423− className='text-mono mt-8'
424− prop-size='lg'
425− prop-format='innerLabel'
426− placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
427− value={ state.receivingAddress }
428− disabled={ !state.available }
429− onChange={ v => this.handleAddressInput(v) }>
430− <InnerLabel>{ 'Receiving Address' }</InnerLabel>
431− <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
432− </AddressInput>
433−
434− <Anchor
435− className={'mt-1'}
436− prop-size={'sm'}
437− prop-disabled={!isValidAddress(state.receivingAddress)}
438− target={'_blank'}
439− href={`https://etherscan.io/address/${state.receivingAddress}`}>
440− {'View on Etherscan ↗'}
441− </Anchor>
405+ <PointInput
406+ prop-size='lg'
407+ prop-format='innerLabel'
408+ className={'mono mt-8'}
409+ placeholder={ `e.g. ${state.suggestions[3]}` }
410+ value={ state.desiredPoint }
411+ onChange={ this.handlePointInput }>
412+ <InnerLabel>{ 'Point to Issue' }</InnerLabel>
413+ <ValidatedSigil
414+ className={'tr-0 mt-05 mr-0 abs'}
415+ patp={state.desiredPoint}
416+ size={76}
417+ margin={8}
418+ validator={() => this.validatePoint(state.desiredPoint)} />
419+ </PointInput>
420+
421+ <AddressInput
422+ className='text-mono mt-8'
423+ prop-size='lg'
424+ prop-format='innerLabel'
425+ placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
426+ value={ state.receivingAddress }
427+ disabled={ !state.available }
428+ onChange={ v => this.handleAddressInput(v) }>
429+ <InnerLabel>{ 'Receiving Address' }</InnerLabel>
430+ <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
431+ </AddressInput>
432+
433+ <Anchor
434+ className={'mt-1'}
435+ prop-size={'sm'}
436+ prop-disabled={!isValidAddress(state.receivingAddress)}
437+ target={'_blank'}
438+ href={`https://etherscan.io/address/${state.receivingAddress}`}>
439+ {'View on Etherscan ↗'}
440+ </Anchor>
442441
443− {
444− props.web3.matchWith({
445− Nothing: _ => <div />,
446− Just: _ =>
447− <Button
448− prop-size='lg wide'
449− prop-color={this.buttonTriState()}
450− className={'mt-8'}
451− disabled={!validPoint}
452− onClick={this.handleConfirmAvailability}>
453− {this.buttonTriStateText()}
454− </Button>
455− })
456− }
442+ {
443+ props.web3.matchWith({
444+ Nothing: _ => <div />,
445+ Just: _ =>
446+ <Button
447+ prop-size='lg wide'
448+ prop-color={this.buttonTriState()}
449+ className={'mt-8'}
450+ disabled={!validPoint}
451+ onClick={this.handleConfirmAvailability}>
452+ {this.buttonTriStateText()}
453+ </Button>
454+ })
455+ }
457456
458− <StatelessTransaction
459− // Upper scope
460− web3={props.web3}
461− contracts={props.contracts}
462− wallet={props.wallet}
463− walletType={props.walletType}
464− walletHdPath={props.walletHdPath}
465− // Tx
466− txn={state.txn}
467− stx={state.stx}
468− // Tx details
469− nonce={state.nonce}
470− gasPrice={state.gasPrice}
471− chainId={state.chainId}
472− gasLimit={state.gasLimit}
473− // Checks
474− userApproval={state.userApproval}
475− canGenerate={ canGenerate }
476− canSign={ canSign }
477− canApprove={ canApprove }
478− canSend={ canSend }
479− // Methods
480− createUnsignedTxn={this.handleCreateUnsignedTxn}
481− setUserApproval={this.handleSetUserApproval}
482− setTxn={this.handleSetTxn}
483− setStx={this.handleSetStx}
484− setNonce={this.handleSetNonce}
485− setChainId={this.handleSetChainId}
486− setGasPrice={this.handleSetGasPrice}
487− setGasLimit={this.handleSetGasLimit}
488− handleSubmit={this.handleSubmit} />
489−
490− </Form>
457+ <StatelessTransaction
458+ // Upper scope
459+ web3={props.web3}
460+ contracts={props.contracts}
461+ wallet={props.wallet}
462+ walletType={props.walletType}
463+ walletHdPath={props.walletHdPath}
464+ // Tx
465+ txn={state.txn}
466+ stx={state.stx}
467+ // Tx details
468+ nonce={state.nonce}
469+ gasPrice={state.gasPrice}
470+ chainId={state.chainId}
471+ gasLimit={state.gasLimit}
472+ // Checks
473+ userApproval={state.userApproval}
474+ canGenerate={ canGenerate }
475+ canSign={ canSign }
476+ canApprove={ canApprove }
477+ canSend={ canSend }
478+ // Methods
479+ createUnsignedTxn={this.handleCreateUnsignedTxn}
480+ setUserApproval={this.handleSetUserApproval}
481+ setTxn={this.handleSetTxn}
482+ setStx={this.handleSetStx}
483+ setNonce={this.handleSetNonce}
484+ setChainId={this.handleSetChainId}
485+ setGasPrice={this.handleSetGasPrice}
486+ setGasLimit={this.handleSetGasLimit}
487+ handleSubmit={this.handleSubmit} />
491488
492489 {
493490 Maybe.Nothing.hasInstance(state.txError)
src/views/Keystore.jsnew file+168
@@ -0,0 +1,168 @@
1+import { Just, Nothing } from 'folktale/maybe'
2+import { Ok, Error } from 'folktale/result'
3+import React from 'react'
4+import * as keythereum from 'keythereum'
5+import { Button, UploadButton } from '../components/Base'
6+import { Input, InnerLabel, InputCaption } from '../components/Base'
7+import { Row, Col, H1, H3, Warning } from '../components/Base'
8+
9+import { BRIDGE_ERROR } from '../lib/error'
10+import { ROUTE_NAMES } from '../lib/router'
11+import { EthereumWallet } from '../lib/wallet'
12+
13+class Keystore extends React.Component {
14+
15+ constructor(props) {
16+ super(props)
17+
18+ this.state = {
19+ keystore: Nothing(), // Maybe<Result<String, String>>
20+ password: '',
21+ decryptionProblem: false
22+ }
23+
24+ this.handleKeystoreUpload = this.handleKeystoreUpload.bind(this)
25+ this.handlePasswordInput = this.handlePasswordInput.bind(this)
26+ this.constructWallet = this.constructWallet.bind(this)
27+ }
28+
29+ handlePasswordInput(password) {
30+ this.setState({ password })
31+ }
32+
33+ constructWallet() {
34+ const { setWallet } = this.props
35+ const { keystore, password } = this.state
36+
37+ try {
38+
39+ const text = keystore.matchWith({
40+ Nothing: _ => { throw BRIDGE_ERROR.MISSING_KEYSTORE },
41+ Just: ks => ks.value.matchWith({
42+ Ok: result => result.value,
43+ Error: _ => { throw BRIDGE_ERROR.MISSING_KEYSTORE }
44+ })
45+ })
46+
47+ const json = JSON.parse(text)
48+ const privateKey = keythereum.recover(password, json)
49+
50+ const wallet = new EthereumWallet(privateKey)
51+ this.setState({ decryptionProblem: false })
52+ setWallet(Just(wallet))
53+
54+ } catch (err) {
55+
56+ this.setState({ decryptionProblem: true })
57+ setWallet(Nothing())
58+
59+ }
60+ }
61+
62+ handleKeystoreUpload = (event) => {
63+ const file = event.files.item(0)
64+ const reader = new FileReader()
65+
66+ reader.onload = (e) => {
67+ const keystore = e.target.result
68+ this.setState({ keystore: Just(Ok(keystore)) })
69+ }
70+
71+ const failure = _ => {
72+ const message = 'There was a problem uploading your Keystore file'
73+ this.setState({ keystore: Just(Error(message)) })
74+ }
75+
76+ reader.onerror = failure
77+ reader.onabort = failure
78+
79+ reader.readAsText(file)
80+ }
81+
82+ render() {
83+ const { pushRoute, popRoute, wallet } = this.props
84+ const { keystore, password, decryptionProblem } = this.state
85+
86+ const uploadButtonClass = keystore.matchWith({
87+ Nothing: _ => 'bg-blue white',
88+ Just: ks => ks.value.matchWith({
89+ Ok: _ => 'bg-green white',
90+ Error: _ => 'bg-yellow black'
91+ })
92+ })
93+
94+ const decryptMessage =
95+ decryptionProblem === false
96+ ? <div />
97+ : <Warning className={'mt-8'}>
98+ <H3 style={{marginTop: 0, paddingTop: 0}}>
99+ {
100+ "Couldn't decrypt wallet."
101+ }
102+ </H3>
103+ { 'You may have entered an incorrect password.' }
104+ </Warning>
105+
106+ return (
107+ <Row>
108+ <Col className={'measure-md'}>
109+ <H1 className={'mb-4'}>{ 'Upload Your Keystore File' }</H1>
110+ <InputCaption>
111+ { `Please upload your Ethereum keystore file. If your keystore
112+ file is encrypted with a password, you'll also need to enter
113+ that below.` }
114+ </InputCaption>
115+
116+ <UploadButton
117+ className={ `btn ${uploadButtonClass} mt-10` }
118+ onChange={ this.handleKeystoreUpload }
119+ >
120+ <div className={'flex-center-all fs-4 h-11 pointer'}>{ 'Upload Keystore file' }</div>
121+ </UploadButton>
122+
123+ <Input
124+ className='pt-8 mt-8'
125+ prop-size='md'
126+ prop-format='innerLabel'
127+ type='password'
128+ name='password'
129+ onChange={ this.handlePasswordInput }
130+ value={ password }
131+ autocomplete='off'
132+ autoFocus>
133+ <InnerLabel>{'Password'}</InnerLabel>
134+ </Input>
135+
136+ <Button
137+ className={` mt-10`}
138+ prop-size={ 'wide lg' }
139+ disabled={ Nothing.hasInstance(keystore) }
140+ onClick={ this.constructWallet }
141+ >
142+ { 'Decrypt' }
143+ </Button>
144+
145+ { decryptMessage }
146+
147+ <Button
148+ className={'mt-10'}
149+ prop-size={ 'wide lg' }
150+ disabled={ Nothing.hasInstance(wallet) }
151+ onClick={ () => {
152+ popRoute()
153+ pushRoute(ROUTE_NAMES.SHIPS)
154+ }
155+ }
156+ >
157+ { 'Continue →' }
158+ </Button>
159+
160+ </Col>
161+ </Row>
162+
163+
164+ )
165+ }
166+}
167+
168+export default Keystore
src/views/Ledger.js+31−33
@@ -3,7 +3,7 @@ import React from 'react'
33import Maybe from 'folktale/maybe'
44import { Button } from '../components/Base'
55import { Row, Col, H1, P, H2 } from '../components/Base'
6−import { Form, Input, InnerLabel } from '../components/Base'
6+import { Input, InnerLabel } from '../components/Base'
77import Transport from '@ledgerhq/hw-transport-u2f'
88import Eth from '@ledgerhq/hw-app-eth'
99import * as secp256k1 from 'secp256k1'
@@ -99,38 +99,36 @@ class Ledger extends React.Component {
9999 <li className={'mt-4'}>{'Run '}<code>{'python bridge-https.py'}</code></li>
100100 </ol>
101101
102− <Form>
103− <Input
104− className='pt-8 mt-8'
105− prop-size='md'
106− prop-format='innerLabel'
107− name='hdpath'
108− value={ addHdPrefix(hdpath) }
109− autocomplete='off'
110− onChange={ this.handleHdPathInput }>
111− <InnerLabel>{'HD Path'}</InnerLabel>
112− </Input>
113−
114− <Button
115− prop-size={'wide lg'}
116− className={'mt-8'}
117− onClick={ this.pollDevice }>
118− { 'Authenticate →' }
119− </Button>
120−
121− <Button
122− className={'mt-8'}
123− prop-size={'wide lg'}
124− disabled={ Maybe.Nothing.hasInstance(wallet) }
125− onClick={
126− () => {
127− popRoute()
128− pushRoute(ROUTE_NAMES.SHIPS)
129− }
130− }>
131− { 'Continue →' }
132− </Button>
133− </Form>
102+ <Input
103+ className='pt-8 mt-8'
104+ prop-size='md'
105+ prop-format='innerLabel'
106+ name='hdpath'
107+ value={ addHdPrefix(hdpath) }
108+ autocomplete='off'
109+ onChange={ this.handleHdPathInput }>
110+ <InnerLabel>{'HD Path'}</InnerLabel>
111+ </Input>
112+
113+ <Button
114+ prop-size={'wide lg'}
115+ className={'mt-8'}
116+ onClick={ this.pollDevice }>
117+ { 'Authenticate →' }
118+ </Button>
119+
120+ <Button
121+ className={'mt-8'}
122+ prop-size={'wide lg'}
123+ disabled={ Maybe.Nothing.hasInstance(wallet) }
124+ onClick={
125+ () => {
126+ popRoute()
127+ pushRoute(ROUTE_NAMES.SHIPS)
128+ }
129+ }>
130+ { 'Continue →' }
131+ </Button>
134132
135133 </Col>
136134 </Row>
src/views/Mnemonic.js+1−3
@@ -3,7 +3,7 @@ import Maybe from 'folktale/maybe'
33import React from 'react'
44import { Button } from '../components/Base'
55import { Input, MnemonicInput, InnerLabel, InputCaption } from '../components/Base'
6−import { Row, Col, H1, Form } from '../components/Base'
6+import { Row, Col, H1 } from '../components/Base'
77
88import { ROUTE_NAMES } from '../lib/router'
99import { DEFAULT_HD_PATH, walletFromMnemonic } from '../lib/wallet'
@@ -78,7 +78,6 @@ class Mnemonic extends React.Component {
7878 <Row>
7979 <Col>
8080 <H1 className={'mb-4'}>{ 'Enter Your Mnemonic' }</H1>
81− <Form>
8281 <InputCaption>
8382 { "Please enter your BIP39 mnemonic here." }
8483 </InputCaption>
@@ -124,7 +123,6 @@ class Mnemonic extends React.Component {
124123 >
125124 { 'Continue →' }
126125 </Button>
127− </Form>
128126 </Col>
129127 </Row>
130128
src/views/PrivateKey.jsnew file+85
@@ -0,0 +1,85 @@
1+import Maybe from 'folktale/maybe'
2+import React from 'react'
3+import { Button } from '../components/Base'
4+import { RequiredInput, InnerLabel, InputCaption } from '../components/Base'
5+import { Row, Col, H1 } from '../components/Base'
6+
7+import { ROUTE_NAMES } from '../lib/router'
8+import { EthereumWallet } from '../lib/wallet'
9+
10+const secre = /[0-9A-Fa-f]{64}/g
11+
12+class PrivateKey extends React.Component {
13+
14+ constructor(props) {
15+ super(props)
16+
17+ this.state = {
18+ privateKey: '',
19+ }
20+
21+ this.handlePrivateKeyInput = this.handlePrivateKeyInput.bind(this)
22+ }
23+
24+ handlePrivateKeyInput(privateKey) {
25+ this.setState({ privateKey })
26+ this.constructWallet(privateKey)
27+ }
28+
29+ constructWallet(privateKey) {
30+ const { setWallet } = this.props
31+ if (secre.test(privateKey) === true) {
32+ const sec = Buffer.from(privateKey, 'hex')
33+ const wallet = new EthereumWallet(sec)
34+ setWallet(Maybe.Just(wallet))
35+ } else {
36+ setWallet(Maybe.Nothing())
37+ }
38+ }
39+
40+ render() {
41+ const { pushRoute, popRoute, wallet } = this.props
42+ const { privateKey } = this.state
43+
44+ return (
45+ <Row>
46+ <Col className={'measure-md'}>
47+ <H1 className={'mb-4'}>{ 'Enter Your Private Key' }</H1>
48+ <InputCaption>
49+ { `Please enter your raw Ethereum private key here.` }
50+ </InputCaption>
51+
52+ <RequiredInput
53+ className='pt-8 mt-8'
54+ prop-size='md'
55+ prop-format='innerLabel'
56+ type='password'
57+ name='privateKey'
58+ onChange={ this.handlePrivateKeyInput }
59+ value={ privateKey }
60+ autocomplete='off'
61+ autoFocus>
62+ <InnerLabel>{'Private Key'}</InnerLabel>
63+ </RequiredInput>
64+
65+ <Button
66+ className={'mt-10'}
67+ prop-size={ 'wide lg' }
68+ disabled={ Maybe.Nothing.hasInstance(wallet) }
69+ onClick={ () => {
70+ popRoute()
71+ pushRoute(ROUTE_NAMES.SHIPS)
72+ }
73+ }
74+ >
75+ { 'Continue →' }
76+ </Button>
77+ </Col>
78+ </Row>
79+
80+
81+ )
82+ }
83+}
84+
85+export default PrivateKey
src/views/SetKeys.js+54−58
@@ -2,7 +2,7 @@ import React from 'react'
22import Maybe from 'folktale/maybe'
33import * as azimuth from 'azimuth-js'
44import * as ob from 'urbit-ob'
5−import { Row, Col, H1, P, Warning, Form, H3 } from '../components/Base'
5+import { Row, Col, H1, P, Warning, H3 } from '../components/Base'
66import { RequiredInput, InnerLabel } from '../components/Base'
77
88import StatelessTransaction from '../components/StatelessTransaction'
@@ -313,63 +313,59 @@ class SetKeys extends React.Component {
313313 </Warning>
314314 : <div /> }
315315
316− <Form>
317− <RequiredInput
318− className='mono'
319− prop-size='lg'
320− prop-format='innerLabel'
321− autoFocus
322− value={ state.networkSeed }
323− onChange={ this.handleNetworkSeedInput }>
324− <InnerLabel>{ 'Network seed' }</InnerLabel>
325− </RequiredInput>
326−
327− <StatelessTransaction
328− // Upper scope
329− web3={props.web3}
330− contracts={props.contracts}
331− wallet={props.wallet}
332− walletType={props.walletType}
333− walletHdPath={props.walletHdPath}
334− // Tx
335− txn={state.txn}
336− stx={state.stx}
337− // Tx details
338− nonce={state.nonce}
339− gasPrice={state.gasPrice}
340− chainId={state.chainId}
341− gasLimit={state.gasLimit}
342− // Checks
343− userApproval={state.userApproval}
344− canGenerate={ canGenerate }
345− canSign={ canSign }
346− canApprove={ canApprove }
347− canSend={ canSend }
348− // Methods
349− createUnsignedTxn={this.handleCreateUnsignedTxn}
350− setUserApproval={this.handleSetUserApproval}
351− setTxn={this.handleSetTxn}
352− setStx={this.handleSetStx}
353− setNonce={this.handleSetNonce}
354− setChainId={this.handleSetChainId}
355− setGasPrice={this.handleSetGasPrice}
356− setGasLimit={this.handleSetGasLimit}
357− handleSubmit={this.handleSubmit} />
358−
359− {
360− Maybe.Nothing.hasInstance(state.txError)
361− ? ''
362− : <Warning className={'mt-8'}>
363− <H3 style={{marginTop: 0, paddingTop: 0}}>
364− {
365− 'There was an error sending your transaction.'
366− }
367− </H3>
368− { state.txError.value }
369− </Warning>
370− }
371−
372− </Form>
316+ <RequiredInput
317+ className='mono'
318+ prop-size='lg'
319+ prop-format='innerLabel'
320+ autoFocus
321+ value={ state.networkSeed }
322+ onChange={ this.handleNetworkSeedInput }>
323+ <InnerLabel>{ 'Network seed' }</InnerLabel>
324+ </RequiredInput>
325+
326+ <StatelessTransaction
327+ // Upper scope
328+ web3={props.web3}
329+ contracts={props.contracts}
330+ wallet={props.wallet}
331+ walletType={props.walletType}
332+ // Tx
333+ txn={state.txn}
334+ stx={state.stx}
335+ // Tx details
336+ nonce={state.nonce}
337+ gasPrice={state.gasPrice}
338+ chainId={state.chainId}
339+ gasLimit={state.gasLimit}
340+ // Checks
341+ userApproval={state.userApproval}
342+ canGenerate={ canGenerate }
343+ canSign={ canSign }
344+ canApprove={ canApprove }
345+ canSend={ canSend }
346+ // Methods
347+ createUnsignedTxn={this.handleCreateUnsignedTxn}
348+ setUserApproval={this.handleSetUserApproval}
349+ setTxn={this.handleSetTxn}
350+ setStx={this.handleSetStx}
351+ setNonce={this.handleSetNonce}
352+ setChainId={this.handleSetChainId}
353+ setGasPrice={this.handleSetGasPrice}
354+ setGasLimit={this.handleSetGasLimit}
355+ handleSubmit={this.handleSubmit} />
356+
357+ {
358+ Maybe.Nothing.hasInstance(state.txError)
359+ ? ''
360+ : <Warning className={'mt-8'}>
361+ <H3 style={{marginTop: 0, paddingTop: 0}}>
362+ {
363+ 'There was an error sending your transaction.'
364+ }
365+ </H3>
366+ { state.txError.value }
367+ </Warning>
368+ }
373369
374370 </Col>
375371 </Row>
src/views/SetProxy.js+66−68
@@ -2,7 +2,7 @@ import Maybe from 'folktale/maybe'
22import React from 'react'
33import * as azimuth from 'azimuth-js'
44import { Row, Col, H1, H3, P, InnerLabel, ShowBlockie, Anchor } from '../components/Base'
5−import { AddressInput, Form, Warning } from '../components/Base'
5+import { AddressInput, Warning } from '../components/Base'
66import * as ob from 'urbit-ob'
77
88import { PROXY_TYPE, renderProxyType } from '../lib/proxy'
@@ -293,73 +293,71 @@ class SetProxy extends React.Component {
293293 'Generator to generate a keypair.'
294294 }
295295 </P>
296− <Form>
297− <AddressInput
298− className='mono mt-8'
299− prop-size='lg'
300− prop-format='innerLabel'
301− placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
302− value={ state.proxyAddress }
303− onChange={ v => this.handleAddressInput(v) }>
304− <InnerLabel>{ 'Proxy Address' }</InnerLabel>
305− <ShowBlockie className={'mt-1'} address={state.proxyAddress} />
306− </AddressInput>
307−
308− <Anchor
309− className={'mt-1'}
310− prop-size={'sm'}
311− disabled={!isValidAddress(state.proxyAddress)}
312− target={'_blank'}
313− href={`https://etherscan.io/address/${state.proxyAddress}`}>
314− {'View on Etherscan ↗'}
315− </Anchor>
316−
317− <StatelessTransaction
318− // Upper scope
319− web3={props.web3}
320− contracts={props.contracts}
321− wallet={props.wallet}
322− walletType={props.walletType}
323− walletHdPath={props.walletHdPath}
324− // Tx
325− txn={state.txn}
326− stx={state.stx}
327− // Tx details
328− nonce={state.nonce}
329− gasPrice={state.gasPrice}
330− chainId={state.chainId}
331− gasLimit={state.gasLimit}
332− // Checks
333− userApproval={state.userApproval}
334− canGenerate={ canGenerate }
335− canSign={ canSign }
336− canApprove={ canApprove }
337− canSend={ canSend }
338− // Methods
339− createUnsignedTxn={this.handleCreateUnsignedTxn}
340− setUserApproval={this.handleSetUserApproval}
341− setTxn={this.handleSetTxn}
342− setStx={this.handleSetStx}
343− setNonce={this.handleSetNonce}
344− setChainId={this.handleSetChainId}
345− setGasPrice={this.handleSetGasPrice}
346− setGasLimit={this.handleSetGasLimit}
347− handleSubmit={this.handleSubmit} />
348−
349− {
350− Maybe.Nothing.hasInstance(state.txError)
351− ? ''
352− : <Warning className={'mt-8'}>
353− <H3 style={{marginTop: 0, paddingTop: 0}}>
354− {
355− 'There was an error sending your transaction.'
356− }
357− </H3>
358− { state.txError.value }
359− </Warning>
360− }
361−
362− </Form>
296+ <AddressInput
297+ className='mono mt-8'
298+ prop-size='lg'
299+ prop-format='innerLabel'
300+ placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
301+ value={ state.proxyAddress }
302+ onChange={ v => this.handleAddressInput(v) }>
303+ <InnerLabel>{ 'Proxy Address' }</InnerLabel>
304+ <ShowBlockie className={'mt-1'} address={state.proxyAddress} />
305+ </AddressInput>
306+
307+ <Anchor
308+ className={'mt-1'}
309+ prop-size={'sm'}
310+ disabled={!isValidAddress(state.proxyAddress)}
311+ target={'_blank'}
312+ href={`https://etherscan.io/address/${state.proxyAddress}`}>
313+ {'View on Etherscan ↗'}
314+ </Anchor>
315+
316+ <StatelessTransaction
317+ // Upper scope
318+ web3={props.web3}
319+ contracts={props.contracts}
320+ wallet={props.wallet}
321+ walletType={props.walletType}
322+ walletHdPath={props.walletHdPath}
323+ // Tx
324+ txn={state.txn}
325+ stx={state.stx}
326+ // Tx details
327+ nonce={state.nonce}
328+ gasPrice={state.gasPrice}
329+ chainId={state.chainId}
330+ gasLimit={state.gasLimit}
331+ // Checks
332+ userApproval={state.userApproval}
333+ canGenerate={ canGenerate }
334+ canSign={ canSign }
335+ canApprove={ canApprove }
336+ canSend={ canSend }
337+ // Methods
338+ createUnsignedTxn={this.handleCreateUnsignedTxn}
339+ setUserApproval={this.handleSetUserApproval}
340+ setTxn={this.handleSetTxn}
341+ setStx={this.handleSetStx}
342+ setNonce={this.handleSetNonce}
343+ setChainId={this.handleSetChainId}
344+ setGasPrice={this.handleSetGasPrice}
345+ setGasLimit={this.handleSetGasLimit}
346+ handleSubmit={this.handleSubmit} />
347+
348+ {
349+ Maybe.Nothing.hasInstance(state.txError)
350+ ? ''
351+ : <Warning className={'mt-8'}>
352+ <H3 style={{marginTop: 0, paddingTop: 0}}>
353+ {
354+ 'There was an error sending your transaction.'
355+ }
356+ </H3>
357+ { state.txError.value }
358+ </Warning>
359+ }
360+
363361 </Col>
364362 </Row>
365363 )
src/views/Shards.js+77−80
@@ -2,7 +2,7 @@ import Maybe from 'folktale/maybe'
22import React from 'react'
33import { Button } from '../components/Base'
44import { InnerLabel, ValidatedSigil, PointInput, ShardInput } from '../components/Base'
5−import { Row, Col, H1, P, Form } from '../components/Base'
5+import { Row, Col, H1, P } from '../components/Base'
66import * as kg from '../../node_modules/urbit-key-generation/dist/index'
77import * as ob from 'urbit-ob'
88
@@ -115,87 +115,84 @@ class Shards extends React.Component {
115115
116116 </P>
117117
118− <Form>
119−
120− <PointInput
121− className='mono mt-8'
122− prop-size='lg'
123− prop-format='innerLabel'
124− type='text'
125− autoFocus
126− placeholder={ `e.g. ${phPoint}` }
127− value={ pointName }
128− onChange={ this.handlePointNameInput }>
129− <InnerLabel>{ 'Point' }</InnerLabel>
130− <ValidatedSigil
131− className={'tr-0 mt-05 mr-0 abs'}
132− patp={pointName}
133− size={68}
134− margin={8} />
135− </PointInput>
136−
137−
138− <ShardInput
139− className='mono mt-8'
140− prop-size='md'
141− prop-format='innerLabel'
142− type='text'
143− name='shard1'
144− placeholder={ `e.g. ${phTick}` }
145− value={ shard1 }
146− onChange={ inp => this.handleShardInput(SHARDS.SHARD1, inp) }>
147− <InnerLabel>{ 'Shard 1' }</InnerLabel>
148− </ShardInput>
149−
150− <ShardInput
151− className='mono mt-8'
152− prop-size='md'
153− prop-format='innerLabel'
154− type='text'
155− name='shard2'
156− placeholder={ `e.g. ${phTick}` }
157− value={ shard2 }
158− onChange={ inp => this.handleShardInput(SHARDS.SHARD2, inp) }>
159− <InnerLabel>{ 'Shard 2' }</InnerLabel>
160− </ShardInput>
161−
162− <ShardInput
163− className='mono mt-8'
164− prop-size='md'
165− prop-format='innerLabel'
166− type='text'
167− name='shard3'
168− placeholder={ `e.g. ${phTick}` }
169− value={ shard3 }
170− onChange={ inp => this.handleShardInput(SHARDS.SHARD3, inp) }>
171− <InnerLabel>{ 'Shard 3' }</InnerLabel>
172− </ShardInput>
173−
174− <Button
175− disabled={ !ready }
176− className={'mt-8'}
177− prop-size={'lg wide'}
178− // prop-color={this.buttonTriState(wallet)}
179− onClick={() =>
180− this.walletFromShards(shard1, shard2, shard3, pointName)
181− }>
182− {'Unlock Wallet →'}
183− </Button>
184−
185− <Button
186− className={'mt-4'}
187− prop-size={'xl wide'}
188− disabled={ Maybe.Nothing.hasInstance(wallet) }
189− onClick={ () => {
190− popRoute()
191− pushRoute(ROUTE_NAMES.SHIPS)
192− }
118+ <PointInput
119+ className='mono mt-8'
120+ prop-size='lg'
121+ prop-format='innerLabel'
122+ type='text'
123+ autoFocus
124+ placeholder={ `e.g. ${phPoint}` }
125+ value={ pointName }
126+ onChange={ this.handlePointNameInput }>
127+ <InnerLabel>{ 'Point' }</InnerLabel>
128+ <ValidatedSigil
129+ className={'tr-0 mt-05 mr-0 abs'}
130+ patp={pointName}
131+ size={68}
132+ margin={8} />
133+ </PointInput>
134+
135+
136+ <ShardInput
137+ className='mono mt-8'
138+ prop-size='md'
139+ prop-format='innerLabel'
140+ type='text'
141+ name='shard1'
142+ placeholder={ `e.g. ${phTick}` }
143+ value={ shard1 }
144+ onChange={ inp => this.handleShardInput(SHARDS.SHARD1, inp) }>
145+ <InnerLabel>{ 'Shard 1' }</InnerLabel>
146+ </ShardInput>
147+
148+ <ShardInput
149+ className='mono mt-8'
150+ prop-size='md'
151+ prop-format='innerLabel'
152+ type='text'
153+ name='shard2'
154+ placeholder={ `e.g. ${phTick}` }
155+ value={ shard2 }
156+ onChange={ inp => this.handleShardInput(SHARDS.SHARD2, inp) }>
157+ <InnerLabel>{ 'Shard 2' }</InnerLabel>
158+ </ShardInput>
159+
160+ <ShardInput
161+ className='mono mt-8'
162+ prop-size='md'
163+ prop-format='innerLabel'
164+ type='text'
165+ name='shard3'
166+ placeholder={ `e.g. ${phTick}` }
167+ value={ shard3 }
168+ onChange={ inp => this.handleShardInput(SHARDS.SHARD3, inp) }>
169+ <InnerLabel>{ 'Shard 3' }</InnerLabel>
170+ </ShardInput>
171+
172+ <Button
173+ disabled={ !ready }
174+ className={'mt-8'}
175+ prop-size={'lg wide'}
176+ // prop-color={this.buttonTriState(wallet)}
177+ onClick={() =>
178+ this.walletFromShards(shard1, shard2, shard3, pointName)
179+ }>
180+ {'Unlock Wallet →'}
181+ </Button>
182+
183+ <Button
184+ className={'mt-4'}
185+ prop-size={'xl wide'}
186+ disabled={ Maybe.Nothing.hasInstance(wallet) }
187+ onClick={ () => {
188+ popRoute()
189+ pushRoute(ROUTE_NAMES.SHIPS)
193190 }
194− >
195− { 'Continue →' }
196− </Button>
191+ }
192+ >
193+ { 'Continue →' }
194+ </Button>
197195
198− </Form>
199196 </Col>
200197 </Row>
201198 )
src/views/Ticket.js+50−53
@@ -2,7 +2,7 @@ import Maybe from 'folktale/maybe'
22import React from 'react'
33import { Button } from '../components/Base'
44import { InnerLabel, ValidatedSigil, PointInput, TicketInput } from '../components/Base'
5−import { Row, Col, H1, P, Form } from '../components/Base'
5+import { Row, Col, H1, P } from '../components/Base'
66import * as kg from '../../node_modules/urbit-key-generation/dist/index'
77import * as ob from 'urbit-ob'
88
@@ -79,60 +79,57 @@ class Ticket extends React.Component {
7979 { `Please enter your point and Urbit master ticket here. This information is written on your Urbit HD paper wallets.` }
8080 </P>
8181
82− <Form>
83−
84− <PointInput
85− className='mono mt-8'
86− prop-size='lg'
87− prop-format='innerLabel'
88− type='text'
89− autoFocus
90− placeholder={ `e.g. ${phPoint}` }
91− value={ pointName }
92− onChange={ this.handlePointNameInput }>
93− <InnerLabel>{ 'Point' }</InnerLabel>
94− <ValidatedSigil
95− className={'tr-0 mt-05 mr-0 abs'}
96− patp={pointName}
97− size={68}
98− margin={8} />
99− </PointInput>
100−
101−
102− <TicketInput
103− className='mono mt-8'
104− prop-size='md'
105− prop-format='innerLabel'
106− type='text'
107− name='ticket'
108− placeholder={ `e.g. ${phTick}` }
109− value={ ticket }
110− onChange={ this.handleTicketInput }>
111− <InnerLabel>{ 'Ticket' }</InnerLabel>
112− </TicketInput>
113−
114− <Button
115− className={'mt-8'}
116− prop-size={'lg wide'}
117− // prop-color={this.buttonTriState(wallet)}
118− onClick={() => this.walletFromTicket(ticket, pointName)}>
119− {'Unlock Wallet →'}
120− </Button>
121−
122− <Button
123− className={'mt-4'}
124− prop-size={'xl wide'}
125− disabled={ Maybe.Nothing.hasInstance(wallet) }
126− onClick={ () => {
127− popRoute()
128− pushRoute(ROUTE_NAMES.SHIPS)
129− }
82+ <PointInput
83+ className='mono mt-8'
84+ prop-size='lg'
85+ prop-format='innerLabel'
86+ type='text'
87+ autoFocus
88+ placeholder={ `e.g. ${phPoint}` }
89+ value={ pointName }
90+ onChange={ this.handlePointNameInput }>
91+ <InnerLabel>{ 'Point' }</InnerLabel>
92+ <ValidatedSigil
93+ className={'tr-0 mt-05 mr-0 abs'}
94+ patp={pointName}
95+ size={68}
96+ margin={8} />
97+ </PointInput>
98+
99+
100+ <TicketInput
101+ className='mono mt-8'
102+ prop-size='md'
103+ prop-format='innerLabel'
104+ type='text'
105+ name='ticket'
106+ placeholder={ `e.g. ${phTick}` }
107+ value={ ticket }
108+ onChange={ this.handleTicketInput }>
109+ <InnerLabel>{ 'Ticket' }</InnerLabel>
110+ </TicketInput>
111+
112+ <Button
113+ className={'mt-8'}
114+ prop-size={'lg wide'}
115+ // prop-color={this.buttonTriState(wallet)}
116+ onClick={() => this.walletFromTicket(ticket, pointName)}>
117+ {'Unlock Wallet →'}
118+ </Button>
119+
120+ <Button
121+ className={'mt-4'}
122+ prop-size={'xl wide'}
123+ disabled={ Maybe.Nothing.hasInstance(wallet) }
124+ onClick={ () => {
125+ popRoute()
126+ pushRoute(ROUTE_NAMES.SHIPS)
130127 }
131− >
132− { 'Continue →' }
133− </Button>
128+ }
129+ >
130+ { 'Continue →' }
131+ </Button>
134132
135− </Form>
136133 </Col>
137134 </Row>
138135 )
src/views/Transfer.js+52−55
@@ -4,7 +4,7 @@ import * as azimuth from 'azimuth-js'
44import * as ob from 'urbit-ob'
55
66import { Row, Col, H1, InnerLabel, ShowBlockie, P, Anchor } from '../components/Base'
7−import { AddressInput, Form, Warning, H3 } from '../components/Base'
7+import { AddressInput, Warning, H3 } from '../components/Base'
88import StatelessTransaction from '../components/StatelessTransaction'
99
1010import { BRIDGE_ERROR } from '../lib/error'
@@ -265,60 +265,57 @@ class Transfer extends React.Component {
265265 You may cancel the transfer until the transfer is accepted.`
266266 }
267267 </P>
268− <Form>
269− <AddressInput
270− className={'mono mt-8'}
271− prop-size='lg'
272− prop-format='innerLabel'
273− value={ state.receivingAddress }
274− placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
275− onChange={ v => this.handleAddressInput(v) }>
276− <InnerLabel>{ 'New ownership address' }</InnerLabel>
277− <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
278− </AddressInput>
279−
280− <Anchor
281− className={'mt-1'}
282− prop-size={'s'}
283− disabled={!isValidAddress(state.receivingAddress)}
284− target={'_blank'}
285− href={`https://etherscan.io/address/${state.receivingAddress}`}>
286− {'View on Etherscan ↗'}
287− </Anchor>
288−
289− <StatelessTransaction
290− // Upper scope
291− web3={props.web3}
292− contracts={props.contracts}
293− wallet={props.wallet}
294− walletType={props.walletType}
295− walletHdPath={props.walletHdPath}
296− // Tx
297− txn={state.txn}
298− stx={state.stx}
299− // Tx details
300− nonce={state.nonce}
301− gasPrice={state.gasPrice}
302− chainId={state.chainId}
303− gasLimit={state.gasLimit}
304− // Checks
305− userApproval={state.userApproval}
306− canGenerate={ canGenerate }
307− canSign={ canSign }
308− canApprove={ canApprove }
309− canSend={ canSend }
310− // Methods
311− createUnsignedTxn={this.handleCreateUnsignedTxn}
312− setUserApproval={this.handleSetUserApproval}
313− setTxn={this.handleSetTxn}
314− setStx={this.handleSetStx}
315− setNonce={this.handleSetNonce}
316− setChainId={this.handleSetChainId}
317− setGasPrice={this.handleSetGasPrice}
318− setGasLimit={this.handleSetGasLimit}
319− handleSubmit={this.handleSubmit} />
320−
321− </Form>
268+ <AddressInput
269+ className={'mono mt-8'}
270+ prop-size='lg'
271+ prop-format='innerLabel'
272+ value={ state.receivingAddress }
273+ placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
274+ onChange={ v => this.handleAddressInput(v) }>
275+ <InnerLabel>{ 'New ownership address' }</InnerLabel>
276+ <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
277+ </AddressInput>
278+
279+ <Anchor
280+ className={'mt-1'}
281+ prop-size={'s'}
282+ disabled={!isValidAddress(state.receivingAddress)}
283+ target={'_blank'}
284+ href={`https://etherscan.io/address/${state.receivingAddress}`}>
285+ {'View on Etherscan ↗'}
286+ </Anchor>
287+
288+ <StatelessTransaction
289+ // Upper scope
290+ web3={props.web3}
291+ contracts={props.contracts}
292+ wallet={props.wallet}
293+ walletType={props.walletType}
294+ walletHdPath={props.walletHdPath}
295+ // Tx
296+ txn={state.txn}
297+ stx={state.stx}
298+ // Tx details
299+ nonce={state.nonce}
300+ gasPrice={state.gasPrice}
301+ chainId={state.chainId}
302+ gasLimit={state.gasLimit}
303+ // Checks
304+ userApproval={state.userApproval}
305+ canGenerate={ canGenerate }
306+ canSign={ canSign }
307+ canApprove={ canApprove }
308+ canSend={ canSend }
309+ // Methods
310+ createUnsignedTxn={this.handleCreateUnsignedTxn}
311+ setUserApproval={this.handleSetUserApproval}
312+ setTxn={this.handleSetTxn}
313+ setStx={this.handleSetStx}
314+ setNonce={this.handleSetNonce}
315+ setChainId={this.handleSetChainId}
316+ setGasPrice={this.handleSetGasPrice}
317+ setGasLimit={this.handleSetGasLimit}
318+ handleSubmit={this.handleSubmit} />
322319
323320 {
324321 Maybe.Nothing.hasInstance(state.txError)
src/views/Trezor.js+31−34
@@ -3,7 +3,7 @@ import React from 'react'
33import Maybe from 'folktale/maybe'
44import { Button } from '../components/Base'
55import { Row, Col, H1, P } from '../components/Base'
6−import { Form, InnerLabel, Input } from '../components/Base'
6+import { InnerLabel, Input } from '../components/Base'
77import TrezorConnect from 'trezor-connect'
88import * as secp256k1 from 'secp256k1'
99
@@ -62,39 +62,36 @@ class Trezor extends React.Component {
6262 }
6363 </P>
6464
65− <Form>
66− <Input
67− className='pt-8'
68− prop-size='md'
69− prop-format='innerLabel'
70− name='hdpath'
71− value={ hdpath }
72− autocomplete='off'
73− onChange={ this.handleHdPathInput }>
74− <InnerLabel>{'HD Path'}</InnerLabel>
75− </Input>
76−
77− <Button
78− className={'mt-8'}
79− prop-size={'wide lg'}
80− onClick={ this.pollDevice }>
81− { 'Authenticate →' }
82− </Button>
83−
84− <Button
85− className={'mt-8'}
86− prop-size={'wide lg'}
87− disabled={ Maybe.Nothing.hasInstance(wallet) }
88− onClick={
89− () => {
90− popRoute()
91− pushRoute(ROUTE_NAMES.SHIPS)
92− }
93− }>
94− { 'Continue →' }
95− </Button>
96−
97− </Form>
65+ <Input
66+ className='pt-8'
67+ prop-size='md'
68+ prop-format='innerLabel'
69+ name='hdpath'
70+ value={ hdpath }
71+ autocomplete='off'
72+ onChange={ this.handleHdPathInput }>
73+ <InnerLabel>{'HD Path'}</InnerLabel>
74+ </Input>
75+
76+ <Button
77+ className={'mt-8'}
78+ prop-size={'wide lg'}
79+ onClick={ this.pollDevice }>
80+ { 'Authenticate →' }
81+ </Button>
82+
83+ <Button
84+ className={'mt-8'}
85+ prop-size={'wide lg'}
86+ disabled={ Maybe.Nothing.hasInstance(wallet) }
87+ onClick={
88+ () => {
89+ popRoute()
90+ pushRoute(ROUTE_NAMES.SHIPS)
91+ }
92+ }>
93+ { 'Continue →' }
94+ </Button>
9895
9996 </Col>
10097 </Row>
src/views/ViewPoint.js+29−31
@@ -4,7 +4,7 @@ import * as ob from 'urbit-ob'
44
55import { Button } from '../components/Base'
66import { Row, Col, H1, P } from '../components/Base'
7−import { InnerLabel, PointInput, ValidatedSigil, Form } from '../components/Base'
7+import { InnerLabel, PointInput, ValidatedSigil } from '../components/Base'
88
99import { ROUTE_NAMES } from '../lib/router'
1010
@@ -48,38 +48,36 @@ class ViewPoint extends React.Component {
4848 { "Enter a point name to view its public information." }
4949 </P>
5050
51− <Form>
52− <PointInput
53− autoFocus
54− prop-size='lg'
55− prop-format='innerLabel'
56− className='mono'
57− placeholder='e.g. ~zod'
58− onChange={ this.handlePointInput }
59− value={pointName}>
60− <InnerLabel>{'Point Name'}</InnerLabel>
61− <ValidatedSigil
62− className={'tr-0 mt-05 mr-0 abs'}
63− patp={pointName}
64− show
65− size={68}
66− margin={8} />
67− </PointInput>
51+ <PointInput
52+ autoFocus
53+ prop-size='lg'
54+ prop-format='innerLabel'
55+ className='mono'
56+ placeholder='e.g. ~zod'
57+ onChange={ this.handlePointInput }
58+ value={pointName}>
59+ <InnerLabel>{'Point Name'}</InnerLabel>
60+ <ValidatedSigil
61+ className={'tr-0 mt-05 mr-0 abs'}
62+ patp={pointName}
63+ show
64+ size={68}
65+ margin={8} />
66+ </PointInput>
6867
69− <Button
70− className={'mt-8'}
71− disabled={ valid === false }
72− onClick={
73− () => {
74− setPointCursor(Maybe.Just(ob.patp2dec(pointName)))
75− popRoute()
76− pushRoute(ROUTE_NAMES.SHIP)
77− }
68+ <Button
69+ className={'mt-8'}
70+ disabled={ valid === false }
71+ onClick={
72+ () => {
73+ setPointCursor(Maybe.Just(ob.patp2dec(pointName)))
74+ popRoute()
75+ pushRoute(ROUTE_NAMES.SHIP)
7876 }
79− >
80− { 'Continue →' }
81− </Button>
82− </Form>
77+ }
78+ >
79+ { 'Continue →' }
80+ </Button>
8381 </Col>
8482 </Row>
8583 )
src/views/Wallet.js+23−8
@@ -83,14 +83,6 @@ class Wallet extends React.Component {
8383
8484 <DropdownDivider />
8585
86− <DropdownItem
87− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.MNEMONIC)) }
88− >
89− { 'BIP39 Mnemonic' }
90− </DropdownItem>
91−
92− <DropdownDivider />
93−
9486 <DropdownItem
9587 onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.LEDGER)) }
9688 >
@@ -109,6 +101,25 @@ class Wallet extends React.Component {
109101 { 'Trezor' }
110102 </DropdownItem>
111103
104+ <DropdownDivider />
105+
106+ <DropdownItem
107+ onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.MNEMONIC)) }
108+ >
109+ { 'BIP39 Mnemonic' }
110+ </DropdownItem>
111+
112+ <DropdownItem
113+ onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.PRIVATE_KEY)) }
114+ >
115+ { 'Ethereum Private Key' }
116+ </DropdownItem>
117+
118+ <DropdownItem
119+ onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.KEYSTORE)) }
120+ >
121+ { 'Ethereum Keystore File' }
122+ </DropdownItem>
112123
113124
114125
@@ -128,6 +139,10 @@ class Wallet extends React.Component {
128139 ? ROUTE_NAMES.LEDGER
129140 : props.walletType === WALLET_NAMES.TREZOR
130141 ? ROUTE_NAMES.TREZOR
142+ : props.walletType === WALLET_NAMES.PRIVATE_KEY
143+ ? ROUTE_NAMES.PRIVATE_KEY
144+ : props.walletType === WALLET_NAMES.KEYSTORE
145+ ? ROUTE_NAMES.KEYSTORE
131146 : ROUTE_NAMES.DEFAULT
132147 )
133148 }