on master help sign in
- Merged from master
729b8fdChris Johnson
package-lock.json+45−16
@@ -1,6 +1,6 @@
1{
1{
2 "name": "bridge",
2 "name": "bridge",
3− "version": "1.1.0",
3+ "version": "1.2.1",
4 "lockfileVersion": 1,
4 "lockfileVersion": 1,
5 "requires": true,
5 "requires": true,
6 "dependencies": {
6 "dependencies": {
@@ -18700,24 +18700,27 @@
18700 "integrity": "sha512-zbfndGg//cMW4aytXuF4dkBuUu/SUBA94U2zkmDoRwcplS9hCCn/S3VDp4A7CXw5tVxeCm9F6zdfHzWzei6nXg=="
18700 "integrity": "sha512-zbfndGg//cMW4aytXuF4dkBuUu/SUBA94U2zkmDoRwcplS9hCCn/S3VDp4A7CXw5tVxeCm9F6zdfHzWzei6nXg=="
18701 },
18701 },
18702 "trezor-connect": {
18702 "trezor-connect": {
18703− "version": "6.0.3",
18703+ "version": "7.0.2",
18704− "resolved": "https://registry.npmjs.org/trezor-connect/-/trezor-connect-6.0.3.tgz",
18704+ "resolved": "https://registry.npmjs.org/trezor-connect/-/trezor-connect-7.0.2.tgz",
18705− "integrity": "sha512-QqgnulN9uQstVjxYTF9+gcNk01kBUQA4xXy2abyPOgxh8Wio9iiSGftQa12KkFfWLZKkZD1sL03KvQ2ievL05g==",
18705+ "integrity": "sha512-KAFOqxEHHaFvrG8NGLFlM/QxHcwIa3gwfXcgTjCYM0g0zRpwIQBwe35AKsjAQO5yiTJQGa0Cu5MZufGJRGYjjw==",
18706 "requires": {
18706 "requires": {
18707− "babel-runtime": "^6.26.0",
18707+ "@babel/runtime": "^7.3.1",
18708− "events": "^1.1.1",
18708+ "events": "^3.0.0",
18709− "whatwg-fetch": "^2.0.4"
18709+ "whatwg-fetch": "^3.0.0"
18710 },
18710 },
18711 "dependencies": {
18711 "dependencies": {
18712− "events": {
18712+ "@babel/runtime": {
18713− "version": "1.1.1",
18713+ "version": "7.4.3",
18714− "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
18714+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz",
18715− "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
18715+ "integrity": "sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==",
18716+ "requires": {
18717+ "regenerator-runtime": "^0.13.2"
18718+ }
18716 },
18719 },
18717− "whatwg-fetch": {
18720+ "regenerator-runtime": {
18718− "version": "2.0.4",
18721+ "version": "0.13.2",
18719− "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
18722+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
18720− "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
18723+ "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
18721 }
18724 }
18722 }
18725 }
18723 },
18726 },
@@ -18791,6 +18794,14 @@
18791 "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
18794 "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
18792 "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
18795 "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
18793 },
18796 },
18797+ "typedarray-to-buffer": {
18798+ "version": "3.1.5",
18799+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
18800+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
18801+ "requires": {
18802+ "is-typedarray": "^1.0.0"
18803+ }
18804+ },
18794 "typeforce": {
18805 "typeforce": {
18795 "version": "1.18.0",
18806 "version": "1.18.0",
18796 "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
18807 "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
@@ -19599,7 +19610,20 @@
19599 "integrity": "sha1-j93qQuGbvyUh7IeVRkV6Yjqdye8=",
19610 "integrity": "sha1-j93qQuGbvyUh7IeVRkV6Yjqdye8=",
19600 "requires": {
19611 "requires": {
19601 "underscore": "1.8.3",
19612 "underscore": "1.8.3",
19602− "web3-core-helpers": "1.0.0-beta.33"
19613+ "web3-core-helpers": "1.0.0-beta.33",
19614+ "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible"
19615+ },
19616+ "dependencies": {
19617+ "websocket": {
19618+ "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
19619+ "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible",
19620+ "requires": {
19621+ "debug": "^2.2.0",
19622+ "nan": "^2.3.3",
19623+ "typedarray-to-buffer": "^3.1.2",
19624+ "yaeti": "^0.0.6"
19625+ }
19626+ }
19603 }
19627 }
19604 },
19628 },
19605 "web3-shh": {
19629 "web3-shh": {
@@ -20700,6 +20724,11 @@
20700 "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
20724 "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
20701 "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
20725 "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
20702 },
20726 },
20727+ "yaeti": {
20728+ "version": "0.0.6",
20729+ "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
20730+ "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
20731+ },
20703 "yallist": {
20732 "yallist": {
20704 "version": "2.1.2",
20733 "version": "2.1.2",
20705 "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
20734 "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
src/Root.js−1
@@ -1,7 +1,6 @@
1import 'babel-polyfill' // required for @ledgerhq/hw-transport-u2f
1import 'babel-polyfill' // required for @ledgerhq/hw-transport-u2f
2
2
3import React from 'react'
3import React from 'react'
4−import ReactDOM from 'react-dom'
5import _ from 'lodash'
4import _ from 'lodash'
6
5
7import Bridge from './bridge/Bridge'
6import Bridge from './bridge/Bridge'
src/bridge/components/StatelessTransaction.js+398−216
@@ -1,244 +1,426 @@
1−import { Nothing } from 'folktale/maybe'
1+import { Nothing, Just } from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3
3
4import { Code, H3 } from './Base'
4import { Code, H3 } from './Base'
5import { Button } from './Base'
5import { Button } from './Base'
6import { CheckboxButton, Input, InnerLabel } from './Base'
6import { CheckboxButton, Input, InnerLabel } from './Base'
7+import { Warning } from '../components/Base'
7
8
8−import { renderSignedTx, signTransaction } from '../lib/txn'
9+import { addressFromSecp256k1Public } from '../lib/wallet'
10+import { BRIDGE_ERROR } from '../lib/error'
11+import { ROUTE_NAMES } from '../lib/router'
9
12
13+import {
14+ sendSignedTransaction,
15+ fromWei,
16+ renderSignedTx,
17+ signTransaction
18+ } from '../lib/txn'
19+
10−const StatelessTransaction = props => {
20+class StatelessTransaction extends React.Component {
11− const { web3, gasPrice, gasLimit, nonce, chainId } = props
21+
12− const { setNonce, setChainId, setGasLimit, setGasPrice, showGasDetails, toggleGasDetails } = props
22+ constructor(props) {
23+ super(props)
24+
25+ this.state = {
26+ gasPrice: '5',
27+ gasLimit: '600000',
28+ showGasDetails: false,
29+ userApproval: false,
30+ chainId: '',
31+ nonce: '',
32+ stx: Nothing(),
33+ txn: Nothing(),
34+ txError: Nothing(),
35+ }
36+
13− const { txn, stx, createUnsignedTxn } = props
37+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
14− const { canSign, canGenerate, canApprove } = props
38+ this.submit = this.submit.bind(this)
15− const { setUserApproval, userApproval } = props
39+ this.setUserApproval = this.setUserApproval.bind(this)
16− const { canSend, handleSubmit } = props
40+ this.setTxn = this.setTxn.bind(this)
41+ this.setStx = this.setStx.bind(this)
42+ this.sendTxn = this.sendTxn.bind(this)
43+ this.setNonce = this.setNonce.bind(this)
44+ this.setChainId = this.setChainId.bind(this)
45+ this.setGasPrice = this.setGasPrice.bind(this)
46+ this.setGasLimit = this.setGasLimit.bind(this)
47+ this.rangeChange = this.rangeChange.bind(this)
48+ this.toggleGasDetails = this.toggleGasDetails.bind(this)
49+ }
50+
51+ componentDidMount() {
52+ const { props } = this
53+
54+ const addr = props.wallet.matchWith({
55+ Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
56+ Nothing: () => {
57+ throw BRIDGE_ERROR.MISSING_WALLET
58+ }
59+ })
60+
61+ props.web3.matchWith({
62+ Nothing: () => {},
63+ Just: (w3) => {
64+ const validWeb3 = w3.value
65+
66+ const getTxMetadata = [
67+ validWeb3.eth.getTransactionCount(addr),
68+ validWeb3.eth.net.getId(),
69+ validWeb3.eth.getGasPrice()
70+ ];
71+
72+ Promise.all(getTxMetadata).then(r => {
73+ const txMetadata = {
74+ nonce: r[0],
75+ chainId: r[1],
76+ gasPrice: fromWei(r[2], 'gwei'),
77+ };
78+
79+ this.setState({...txMetadata})
80+
81+ })
82+ }
83+ });
84+ }
85+
86+ submit(){
87+ const { props, state } = this
88+ sendSignedTransaction(props.web3.value, state.stx)
89+ .then(sent => {
90+ props.setTxnHashCursor(sent)
91+ props.popRoute()
92+
93+ if (props.networkSeed) {
94+ props.setNetworkSeedCache(props.networkSeed)
95+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, {promptKeyfile: true})
96+ } else {
97+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
98+ }
99+ })
100+ .catch(err => {
101+ if (err.map) {
102+ this.setState({ txError: err.map(val => val.merge()) })
103+ } else {
104+ this.setState({ txError: err })
105+ }
106+ })
107+ }
108+
109+ setUserApproval(){
110+ const {state} = this
111+ this.setState({ userApproval: !state.userApproval })
112+ }
113+
114+ toggleGasDetails() {
115+ this.setState({
116+ showGasDetails: !this.state.showGasDetails
117+ })
118+ }
119+
120+ setStx(stx){
121+ this.setState({
122+ stx,
123+ userApproval: false,
124+ })
125+ }
126+
127+ setTxn(txn){
128+ this.setState({ txn })
129+ }
130+
131+ createUnsignedTxn() {
132+ const txn = this.props.createUnsignedTxn()
133+
134+ this.setState({ txn })
135+ }
136+
137+ clearTxn() {
138+ this.setState({
139+ userApproval: false,
140+ txn: Nothing(),
141+ stx: Nothing(),
142+ })
143+ }
144+
145+ setNonce(nonce){
146+ this.setState({ nonce })
147+ this.clearStx()
148+ }
149+
150+ setChainId(chainId){
151+ this.setState({ chainId })
152+ this.clearStx()
153+ }
154+
155+ setGasPrice(gasPrice) {
156+ this.setState({ gasPrice })
157+ this.clearStx()
158+ }
159+
160+ rangeChange(e) {
161+ this.setGasPrice(e.target.value);
162+ }
163+
164+ setGasLimit(gasLimit){
165+ this.setState({ gasLimit })
166+ this.clearStx()
167+ }
168+
169+ clearStx() {
170+ this.setState({
171+ userApproval: false,
172+ stx: Nothing(),
173+ })
174+ }
17
175
18 // TODO: Investigate
176 // TODO: Investigate
19− // setState doesn't seem to work in SetProxy/handleSubmit;
177+ // setState doesn't seem to work in SetProxy/submit;
20 // - TypeError: Cannot read property 'updater' of undefined
178 // - TypeError: Cannot read property 'updater' of undefined
21 // so just modifying the DOM manually here. (https://imgur.com/a/i0Qsyq1)
179 // so just modifying the DOM manually here. (https://imgur.com/a/i0Qsyq1)
22
180
23− const handleRangeChange = (e) => {
24− setGasPrice(e.target.value);
25− }
26−
27− const sendTxn = (e) => {
181+ sendTxn(e) {
28 e.target.setAttribute("disabled", true);
182 e.target.setAttribute("disabled", true);
29 let spinner = e.target.querySelectorAll('.btn-spinner')[0];
183 let spinner = e.target.querySelectorAll('.btn-spinner')[0];
30 spinner.classList.remove('hide');
184 spinner.classList.remove('hide');
31− handleSubmit()
185+ this.submit()
32− }
186+ }
33−
187+
188+ render() {
189+ const { web3, canGenerate } = this.props
190+ const { gasPrice, gasLimit, nonce, chainId,
191+ txn, stx, userApproval, showGasDetails } = this.state
192+
193+ const { setNonce, setChainId, setGasLimit, setGasPrice, toggleGasDetails,
194+ setUserApproval, sendTxn, createUnsignedTxn } = this
195+
196+ const { state } = this
197+
198+ const canSign = Just.hasInstance(txn)
199+ const canApprove = Just.hasInstance(stx)
200+ const canSend = Just.hasInstance(stx) && userApproval === true
201+
34− const generateButtonColor =
202+ const generateButtonColor =
35− Nothing.hasInstance(txn)
203+ Nothing.hasInstance(txn)
36− ? 'blue'
204+ ? 'blue'
37− : 'green'
205+ : 'green'
38−
206+
39− const signerButtonColor =
207+ const signerButtonColor =
40− Nothing.hasInstance(stx)
208+ Nothing.hasInstance(stx)
41− ? 'blue'
209+ ? 'blue'
42− : 'green'
210+ : 'green'
43−
211+
44− const generateTxnButton =
212+ const generateTxnButton =
45− <Button
213+ <Button
46− className={ 'mt-8' }
214+ className={ 'mt-8' }
47− disabled={ !canGenerate }
215+ disabled={ !canGenerate }
48− prop-color={ generateButtonColor }
216+ prop-color={ generateButtonColor }
49− prop-size={ 'lg wide' }
217+ prop-size={ 'lg wide' }
50− onClick={ createUnsignedTxn }
218+ onClick={ createUnsignedTxn }
51− >
219+ >
52− { 'Generate Transaction' }
220+ { 'Generate Transaction' }
53− </Button>
221+ </Button>
54−
222+
55− const unsignedTxnDisplay = txn.matchWith({
223+ const unsignedTxnDisplay = txn.matchWith({
56− Nothing: _ => '',
224+ Nothing: _ => '',
57− Just: tx =>
225+ Just: tx =>
226+ <React.Fragment>
227+ <H3 className={ 'mt-8' }>
228+ { 'Unsigned Transaction' }
229+ </H3>
230+ <Code>
231+ { JSON.stringify(tx.value, null, 2) }
232+ </Code>
233+ </React.Fragment>
234+ })
235+
236+ const gasPriceRangeDialogue = (
58 <React.Fragment>
237 <React.Fragment>
59− <H3 className={ 'mt-8' }>
60− { 'Unsigned Transaction' }
61− </H3>
62− <Code>
63− { JSON.stringify(tx.value, null, 2) }
64− </Code>
65− </React.Fragment>
66− })
67−
68− const gasPriceRangeDialogue = (
69− <React.Fragment>
70− <div className="mt-12 flex space-between align-baseline">
238+ <div className="mt-12 flex space-between align-baseline">
71− <div>
239+ <div>
72− <span>Gas Price:</span>
240+ <span>Gas Price:</span>
73− <span className="ml-4 text-700 text-sm">{gasPrice} gwei</span>
241+ <span className="ml-4 text-700 text-sm">{gasPrice} gwei</span>
242+ </div>
243+ <div className="text-sm">
244+ <span>Max transaction fee: </span>
245+ <span className="text-700">{(gasPrice * gasLimit) / 1000000000} eth</span>
246+ </div>
74 </div>
247 </div>
248+
249+ <input
75− <div className="text-sm">
250+ className="mt-4"
76− <span>Max transaction fee: </span>
251+ type="range"
77− <span className="text-700">{(gasPrice * gasLimit) / 1000000000} eth</span>
252+ min="2"
253+ max="20"
254+ list="gweiVals"
255+ value={gasPrice}
256+ onChange={this.rangeChange}
257+ />
258+
259+ <div className="flex space-between text-sm mb-8">
260+ <div>Cheap</div>
261+ <div>Fast</div>
78 </div>
262 </div>
79− </div>
80−
81− <input
82− className="mt-4"
83− type="range"
84− min="2"
85− max="20"
86− list="gweiVals"
87− value={gasPrice}
88− onChange={handleRangeChange}
89− />
90−
91− <div className="flex space-between text-sm mb-8">
92− <div>Cheap</div>
93− <div>Fast</div>
94− </div>
95− </React.Fragment>
96− )
97−
98− const toggleGasDetailsDialogue = (
99− <a href="javascript:void(0)" onClick={toggleGasDetails}>Gas Details</a>
100− )
101−
102− const gasPriceDialogue =
103− <Input
104− className={ 'mono mt-4' }
105− prop-size={ 'md' }
106− prop-format={ 'innerLabel' }
107− value={ gasPrice }
108− onChange={ setGasPrice }
109− >
110− <InnerLabel>
111− { 'Gas Price (gwei)' }
112− </InnerLabel>
113− </Input>
114−
115− const gasLimitDialogue =
116− <Input
117− className={ 'mono mt-4' }
118− prop-size={ 'md' }
119− prop-format={ 'innerLabel' }
120− value={ gasLimit }
121− onChange={ setGasLimit }>
122− <InnerLabel>
123− { 'Gas Limit' }
124− </InnerLabel>
125− </Input>
126−
127− const nonceDialogue =
128− <Input
129− className={ 'mono mt-4' }
130− prop-size={ 'md' }
131− prop-format={ 'innerLabel' }
132− value={ nonce }
133− onChange={ setNonce }
134− >
135− <InnerLabel>
136− { 'Nonce' }
137− </InnerLabel>
138− </Input>
139−
140− const chainDialogue =
141− <Input
142− className={ 'mono mt-4 mb-8' }
143− prop-size={ 'md' }
144− prop-format={ 'innerLabel' }
145− value={ chainId }
146− onChange={ setChainId }
147− >
148− <InnerLabel>
149− { 'Chain ID' }
150− </InnerLabel>
151− </Input>
152−
153− const onlineParamsDialogue = web3.matchWith({
154− Just: _ => <div />,
155− Nothing: _ =>
156− <React.Fragment>
157− { nonceDialogue }
158− { chainDialogue }
159− </React.Fragment>
160− })
161−
162− const signTxnButton =
163− <Button
164− disabled={ !canSign }
165− className={ 'mt-8' }
166− prop-size={ 'lg wide' }
167− prop-color={ signerButtonColor }
168− onClick={ () => signTransaction(props) }
169− >
170− { 'Sign Transaction' }
171− </Button>
172−
173− const signedTxnDisplay = stx.matchWith({
174− Nothing: _ => '',
175− Just: tx =>
176− <React.Fragment>
177− <H3 className={ 'mt-8' }>
178− { 'Signed Transaction' }
179− </H3>
180− <Code>
181− { JSON.stringify(renderSignedTx(tx.value), null, 2) }
182− </Code>
183− </React.Fragment>
184− })
185−
186− const confirmButton =
187− <CheckboxButton
188− className={ 'mt-8' }
189− disabled={ !canApprove }
190− onClick={ setUserApproval }
191− state={ userApproval }
192− >
193− <div onClick={ setUserApproval }>
194− { `I approve this transaction and wish to send.` }
195− </div>
196− </CheckboxButton>
197−
198− const sendTxnButton =
199− <Button
200− prop-size={ 'xl wide' }
201− className={ 'mt-8' }
202− disabled={ !canSend }
203− onClick={ sendTxn }
204− >
205− <span className="relative">
206− <span className="btn-spinner hide"></span>
207− { 'Send Transaction' }
208− </span>
209− </Button>
210−
211− const sendDialogue = web3.matchWith({
212− Nothing: _ => '',
213− Just: _ =>
214− <React.Fragment>
215− { confirmButton }
216− { sendTxnButton }
217 </React.Fragment>
263 </React.Fragment>
264+ )
265+
266+ const toggleGasDetailsDialogue = (
267+ <span className="text-link" onClick={toggleGasDetails}>Gas Details</span>
218− })
268+ )
269+
270+ const gasPriceDialogue =
271+ <Input
272+ className={ 'mono mt-4' }
273+ prop-size={ 'md' }
274+ prop-format={ 'innerLabel' }
275+ value={ gasPrice }
276+ onChange={ setGasPrice }
277+ >
278+ <InnerLabel>
279+ { 'Gas Price (gwei)' }
280+ </InnerLabel>
281+ </Input>
219
282
220− return (
283+ const gasLimitDialogue =
221− <React.Fragment>
284+ <Input
222− { generateTxnButton }
285+ className={ 'mono mt-4' }
223− { unsignedTxnDisplay }
286+ prop-size={ 'md' }
287+ prop-format={ 'innerLabel' }
288+ value={ gasLimit }
289+ onChange={ setGasLimit }>
290+ <InnerLabel>
291+ { 'Gas Limit' }
292+ </InnerLabel>
293+ </Input>
224
294
225− { gasPriceRangeDialogue }
295+ const nonceDialogue =
226− { toggleGasDetailsDialogue }
296+ <Input
297+ className={ 'mono mt-4' }
298+ prop-size={ 'md' }
299+ prop-format={ 'innerLabel' }
300+ value={ nonce }
301+ onChange={ setNonce }
302+ >
303+ <InnerLabel>
304+ { 'Nonce' }
305+ </InnerLabel>
306+ </Input>
227
307
228− { showGasDetails &&
308+ const chainDialogue =
309+ <Input
310+ className={ 'mono mt-4 mb-8' }
311+ prop-size={ 'md' }
312+ prop-format={ 'innerLabel' }
313+ value={ chainId }
314+ onChange={ setChainId }
315+ >
316+ <InnerLabel>
317+ { 'Chain ID' }
318+ </InnerLabel>
319+ </Input>
320+
321+ const onlineParamsDialogue = web3.matchWith({
229− <div>
322+ Just: _ => <div />,
230− { gasPriceDialogue }
323+ Nothing: _ =>
231− { gasLimitDialogue }
324+ <React.Fragment>
325+ { nonceDialogue }
326+ { chainDialogue }
327+ </React.Fragment>
328+ })
329+
330+ const signTxnButton =
331+ <Button
332+ disabled={ !canSign }
333+ className={ 'mt-8' }
334+ prop-size={ 'lg wide' }
335+ prop-color={ signerButtonColor }
336+ onClick={ () => signTransaction({...this.props, ...this.state, setStx: this.setStx}) }
337+ >
338+ { 'Sign Transaction' }
339+ </Button>
340+
341+ const signedTxnDisplay = stx.matchWith({
342+ Nothing: _ => '',
343+ Just: tx =>
344+ <React.Fragment>
345+ <H3 className={ 'mt-8' }>
346+ { 'Signed Transaction' }
347+ </H3>
348+ <Code>
349+ { JSON.stringify(renderSignedTx(tx.value), null, 2) }
350+ </Code>
351+ </React.Fragment>
352+ })
353+
354+ const confirmButton =
355+ <CheckboxButton
356+ className={ 'mt-8' }
357+ disabled={ !canApprove }
358+ onClick={ setUserApproval }
359+ state={ userApproval }
360+ >
361+ <div onClick={ setUserApproval }>
362+ { `I approve this transaction and wish to send.` }
232 </div>
363 </div>
233− }
364+ </CheckboxButton>
234− { onlineParamsDialogue }
235
365
236− { signTxnButton }
366+ const sendTxnButton =
367+ <Button
368+ prop-size={ 'xl wide' }
369+ className={ 'mt-8' }
370+ disabled={ !canSend }
371+ onClick={ sendTxn }
372+ >
373+ <span className="relative">
374+ <span className="btn-spinner hide"></span>
375+ { 'Send Transaction' }
376+ </span>
377+ </Button>
237
378
379+ const sendDialogue = web3.matchWith({
380+ Nothing: _ => '',
381+ Just: _ =>
382+ <React.Fragment>
383+ { confirmButton }
384+ { sendTxnButton }
385+ </React.Fragment>
386+ })
387+
388+ const txnErrorDialogue = Nothing.hasInstance(state.txError)
389+ ? ''
390+ : <Warning className={'mt-8'}>
391+ <H3 style={{marginTop: 0, paddingTop: 0}}>
392+ {
393+ 'There was an error sending your transaction.'
394+ }
395+ </H3>
396+ { state.txError.value }
397+ </Warning>
398+
399+ return (
400+ <React.Fragment>
401+ { generateTxnButton }
402+ { unsignedTxnDisplay }
403+
404+ { gasPriceRangeDialogue }
405+ { toggleGasDetailsDialogue }
406+
407+ { showGasDetails &&
408+ <div>
409+ { gasPriceDialogue }
410+ { gasLimitDialogue }
411+ </div>
412+ }
413+ { onlineParamsDialogue }
414+
415+ { signTxnButton }
416+
238− { signedTxnDisplay }
417+ { signedTxnDisplay }
239− { sendDialogue }
418+ { sendDialogue }
419+
420+ { txnErrorDialogue }
240− </React.Fragment>
421+ </React.Fragment>
241− )
422+ )
423+ }
242}
424}
243
425
244export default StatelessTransaction
426export default StatelessTransaction
src/bridge/lib/wallet.js+2−2
@@ -86,10 +86,10 @@ const toChecksumAddress = (address) => {
86const eqAddr = (addr0, addr1) =>
86const eqAddr = (addr0, addr1) =>
87 toChecksumAddress(addr0) === toChecksumAddress(addr1)
87 toChecksumAddress(addr0) === toChecksumAddress(addr1)
88
88
89−const walletFromMnemonic = (mnemonic, hdpath) => {
89+const walletFromMnemonic = (mnemonic, hdpath, passphrase) => {
90 const seed =
90 const seed =
91 bip39.validateMnemonic(mnemonic)
91 bip39.validateMnemonic(mnemonic)
92− ? Maybe.Just(bip39.mnemonicToSeed(mnemonic))
92+ ? Maybe.Just(bip39.mnemonicToSeed(mnemonic, passphrase))
93 : Maybe.Nothing()
93 : Maybe.Nothing()
94
94
95 const toWallet = (sd, path) => {
95 const toWallet = (sd, path) => {
src/bridge/style/extend.css+5
@@ -466,6 +466,11 @@ code {
466 cursor: pointer;
466 cursor: pointer;
467}
467}
468
468
469+.text-link {
470+ cursor: pointer;
471+ text-decoration: underline;
472+}
473+
469.keyfile {
474.keyfile {
470 white-space: pre-wrap;
475 white-space: pre-wrap;
471 word-break: break-word;
476 word-break: break-word;
src/bridge/views/AcceptTransfer.js+11−179
@@ -1,6 +1,6 @@
1−import { Just, Nothing } from 'folktale/maybe'
1+import { Just } from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3−import { Row, Col, H1, H3, P, Warning } from '../components/Base'
3+import { Row, Col, H1, P } from '../components/Base'
4import { InnerLabel, AddressInput, ShowBlockie, Anchor } from '../components/Base'
4import { InnerLabel, AddressInput, ShowBlockie, Anchor } from '../components/Base'
5import * as azimuth from 'azimuth-js'
5import * as azimuth from 'azimuth-js'
6import * as ob from 'urbit-ob'
6import * as ob from 'urbit-ob'
@@ -8,8 +8,6 @@ import * as ob from 'urbit-ob'
8import StatelessTransaction from '../components/StatelessTransaction'
8import StatelessTransaction from '../components/StatelessTransaction'
9import { BRIDGE_ERROR } from '../lib/error'
9import { BRIDGE_ERROR } from '../lib/error'
10import { NETWORK_NAMES } from '../lib/network'
10import { NETWORK_NAMES } from '../lib/network'
11−import { ROUTE_NAMES } from '../lib/router'
12−import { sendSignedTransaction, fromWei } from '../lib/txn'
13import { isValidAddress, addressFromSecp256k1Public } from '../lib/wallet'
11import { isValidAddress, addressFromSecp256k1Public } from '../lib/wallet'
14
12
15class AcceptTransfer extends React.Component {
13class AcceptTransfer extends React.Component {
@@ -32,144 +30,17 @@ class AcceptTransfer extends React.Component {
32
30
33 this.state = {
31 this.state = {
34 receivingAddress: receivingAddress,
32 receivingAddress: receivingAddress,
35− txn: Nothing(),
36− txError: Nothing(),
37 incomingPoint: incomingPoint,
33 incomingPoint: incomingPoint,
38− userApproval: false,
39− nonce: '',
40− gasPrice: '5',
41− showGasDetails: false,
42− chainId: '',
43− gasLimit: '600000',
44− stx: Nothing(),
45 }
34 }
46
35
47 this.handleAddressInput = this.handleAddressInput.bind(this)
36 this.handleAddressInput = this.handleAddressInput.bind(this)
48− this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
37+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
49− this.handleSubmit = this.handleSubmit.bind(this)
38+ this.statelessRef = React.createRef();
50− this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
51− this.handleSetTxn = this.handleSetTxn.bind(this)
52− this.handleSetStx = this.handleSetStx.bind(this)
53− this.handleSetNonce = this.handleSetNonce.bind(this)
54− this.handleSetChainId = this.handleSetChainId.bind(this)
55− this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
56− this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
57− this.toggleGasDetails = this.toggleGasDetails.bind(this)
58− }
59−
60− toggleGasDetails() {
61− this.setState({
62− showGasDetails: !this.state.showGasDetails
63− })
64− }
65−
66− componentDidMount() {
67− const { props } = this
68−
69− const addr = props.wallet.matchWith({
70− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
71− Nothing: () => {
72− throw BRIDGE_ERROR.MISSING_WALLET
73− }
74− })
75−
76− props.web3.matchWith({
77− Nothing: () => {},
78− Just: (w3) => {
79− const validWeb3 = w3.value
80−
81− const getTxMetadata = [
82− validWeb3.eth.getTransactionCount(addr),
83− validWeb3.eth.net.getId(),
84− validWeb3.eth.getGasPrice()
85− ];
86−
87− Promise.all(getTxMetadata).then(r => {
88− const txMetadata = {
89− nonce: r[0],
90− chainId: r[1],
91− gasPrice: fromWei(r[2], 'gwei'),
92− };
93−
94− this.setState({...txMetadata})
95−
96− })
97− }
98− });
99 }
39 }
100
40
101 handleAddressInput(proxyAddress) {
41 handleAddressInput(proxyAddress) {
102 this.setState({ proxyAddress })
42 this.setState({ proxyAddress })
103− this.handleClearTxn()
43+ this.statelessRef.current.clearTxn()
104− }
105−
106− handleCreateUnsignedTxn() {
107− const txn = this.createUnsignedTxn()
108− this.setState({ txn })
109− }
110−
111− handleSetUserApproval(){
112− const {state} = this
113− this.setState({ userApproval: !state.userApproval })
114− }
115−
116− handleSetTxn(txn){
117− this.setState({ txn })
118− }
119−
120− handleSetStx(stx){
121− this.setState({
122− stx,
123− userApproval: false,
124− })
125− }
126−
127− handleSetNonce(nonce){
128− this.setState({ nonce })
129− this.handleClearStx()
130− }
131−
132− handleSetChainId(chainId){
133− this.setState({ chainId })
134− this.handleClearStx()
135− }
136−
137− handleSetGasPrice(gasPrice){
138− this.setState({ gasPrice })
139− this.handleClearStx()
140− }
141−
142− handleSetGasLimit(gasLimit){
143− this.setState({ gasLimit })
144− this.handleClearStx()
145− }
146−
147− handleClearStx() {
148− this.setState({
149− userApproval: false,
150− stx: Nothing(),
151− })
152− }
153−
154− handleClearTxn() {
155− this.setState({
156− userApproval: false,
157− txn: Nothing(),
158− stx: Nothing(),
159− })
160− }
161−
162− handleSubmit() {
163− const { props, state } = this
164− sendSignedTransaction(props.web3.value, state.stx)
165− .then(sent => {
166− props.setTxnHashCursor(sent)
167− props.popRoute()
168− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
169− })
170− .catch(err => {
171− this.setState({ txError: err.map(val => val.merge()) })
172− })
173 }
44 }
174
45
175 createUnsignedTxn() {
46 createUnsignedTxn() {
@@ -200,15 +71,9 @@ class AcceptTransfer extends React.Component {
200 }
71 }
201
72
202 render() {
73 render() {
203−
204 const { state, props } = this
74 const { state, props } = this
205 const validAddress = isValidAddress(state.receivingAddress)
75 const validAddress = isValidAddress(state.receivingAddress)
206 const canGenerate = validAddress === true
76 const canGenerate = validAddress === true
207− const canSign = Just.hasInstance(state.txn)
208− const canApprove = Just.hasInstance(state.stx)
209− const canSend =
210− Just.hasInstance(state.stx)
211− && state.userApproval === true
212
77
213 const esvisible =
78 const esvisible =
214 props.networkType === NETWORK_NAMES.ROPSTEN ||
79 props.networkType === NETWORK_NAMES.ROPSTEN ||
@@ -260,46 +125,13 @@ class AcceptTransfer extends React.Component {
260 walletType={props.walletType}
125 walletType={props.walletType}
261 walletHdPath={props.walletHdPath}
126 walletHdPath={props.walletHdPath}
262 networkType={props.networkType}
127 networkType={props.networkType}
263− // Tx
128+ setTxnHashCursor={props.setTxnHashCursor}
264− txn={state.txn}
129+ popRoute={props.popRoute}
265− stx={state.stx}
130+ pushRoute={props.pushRoute}
266− // Tx details
131+ // Other
267− nonce={state.nonce}
268− gasPrice={state.gasPrice}
269− chainId={state.chainId}
270− gasLimit={state.gasLimit}
271− showGasDetails={state.showGasDetails}
272− toggleGasDetails={this.toggleGasDetails}
273− // Checks
274− userApproval={state.userApproval}
275 canGenerate={ canGenerate }
132 canGenerate={ canGenerate }
276− canSign={ canSign }
277− canApprove={ canApprove }
278− canSend={ canSend }
279− // Methods
280− createUnsignedTxn={this.handleCreateUnsignedTxn}
133+ createUnsignedTxn={this.createUnsignedTxn}
281− setUserApproval={this.handleSetUserApproval}
134+ ref={ this.statelessRef } />
282− setTxn={this.handleSetTxn}
283− setStx={this.handleSetStx}
284− setNonce={this.handleSetNonce}
285− setChainId={this.handleSetChainId}
286− setGasPrice={this.handleSetGasPrice}
287− setGasLimit={this.handleSetGasLimit}
288− handleSubmit={this.handleSubmit} />
289−
290− {
291− Nothing.hasInstance(state.txError)
292− ? ''
293− : <Warning className={'mt-8'}>
294− <H3 style={{marginTop: 0, paddingTop: 0}}>
295− {
296− 'There was an error sending your transaction.'
297− }
298− </H3>
299− { state.txError.value }
300− </Warning>
301− }
302−
303 </Col>
135 </Col>
304 </Row>
136 </Row>
305 )
137 )
src/bridge/views/CancelTransfer.js+7−197
@@ -1,23 +1,12 @@
1import Maybe from 'folktale/maybe'
1import Maybe from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3−import { Row, Col, H1, P, Warning, H3 } from '../components/Base'
3+import { Row, Col, H1, P } from '../components/Base'
4import * as azimuth from 'azimuth-js'
4import * as azimuth from 'azimuth-js'
5import * as ob from 'urbit-ob'
5import * as ob from 'urbit-ob'
6
6
7import StatelessTransaction from '../components/StatelessTransaction'
7import StatelessTransaction from '../components/StatelessTransaction'
8import { BRIDGE_ERROR } from '../lib/error'
8import { BRIDGE_ERROR } from '../lib/error'
9import { ETH_ZERO_ADDR } from '../lib/wallet'
9import { ETH_ZERO_ADDR } from '../lib/wallet'
10−import { ROUTE_NAMES } from '../lib/router'
11−
12−import {
13− sendSignedTransaction,
14− fromWei,
15− } from '../lib/txn'
16−
17−import {
18− addressFromSecp256k1Public,
19−} from '../lib/wallet'
20−
21
10
22class CancelTransfer extends React.Component {
11class CancelTransfer extends React.Component {
23 constructor(props) {
12 constructor(props) {
@@ -32,150 +21,10 @@ class CancelTransfer extends React.Component {
32
21
33 this.state = {
22 this.state = {
34 proxyAddress: '',
23 proxyAddress: '',
35− txn: Maybe.Nothing(),
36− txError: Maybe.Nothing(),
37 pointInTransfer: pointInTransfer,
24 pointInTransfer: pointInTransfer,
38− userApproval: false,
39− nonce: '',
40− gasPrice: '5',
41− showGasDetails: false,
42− chainId: '',
43− gasLimit: '600000',
44− stx: Maybe.Nothing(),
45 }
25 }
46− // Transaction
47− this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
48− this.handleSubmit = this.handleSubmit.bind(this)
49− this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
50− this.handleSetTxn = this.handleSetTxn.bind(this)
51− this.handleSetStx = this.handleSetStx.bind(this)
52− this.handleSetNonce = this.handleSetNonce.bind(this)
53− this.handleSetChainId = this.handleSetChainId.bind(this)
54− this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
55− this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
56− this.toggleGasDetails = this.toggleGasDetails.bind(this)
57− }
58
26
59− toggleGasDetails() {
60− this.setState({
61− showGasDetails: !this.state.showGasDetails
62− })
63− }
64−
65− componentDidMount() {
66− const { props } = this
67−
68− const addr = props.wallet.matchWith({
69− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
70− Nothing: () => {
71− throw BRIDGE_ERROR.MISSING_WALLET
72− }
73− })
74−
75− props.web3.matchWith({
76− Nothing: () => {},
77− Just: (w3) => {
78− const validWeb3 = w3.value
79−
80− const getTxMetadata = [
81− validWeb3.eth.getTransactionCount(addr),
82− validWeb3.eth.net.getId(),
83− validWeb3.eth.getGasPrice()
84− ];
85−
86− Promise.all(getTxMetadata).then(r => {
87− const txMetadata = {
88− nonce: r[0],
89− chainId: r[1],
90− gasPrice: fromWei(r[2], 'gwei'),
91− };
92−
93− this.setState({...txMetadata})
94−
95− })
96− }
97− });
98−
99− }
100−
101−
102−
103− handleCreateUnsignedTxn() {
104− const txn = this.createUnsignedTxn()
27+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
105− this.setState({ txn })
106− }
107−
108−
109−
110− handleSetUserApproval(){
111− const {state} = this
112− this.setState({ userApproval: !state.userApproval })
113− }
114−
115−
116−
117− handleSetTxn(txn){
118− this.setState({ txn })
119− }
120−
121−
122−
123− handleSetStx(stx){
124− this.setState({
125− stx,
126− userApproval: false,
127− })
128− }
129−
130−
131−
132− handleSetNonce(nonce){
133− this.setState({ nonce })
134− this.handleClearStx()
135− }
136−
137−
138−
139− handleSetChainId(chainId){
140− this.setState({ chainId })
141− this.handleClearStx()
142− }
143−
144−
145−
146− handleSetGasPrice(gasPrice){
147− this.setState({ gasPrice })
148− this.handleClearStx()
149− }
150−
151−
152−
153− handleSetGasLimit(gasLimit){
154− this.setState({ gasLimit })
155− this.handleClearStx()
156− }
157−
158−
159−
160− handleClearStx() {
161− this.setState({
162− userApproval: false,
163− stx: Maybe.Nothing(),
164− })
165− }
166−
167−
168− handleSubmit(){
169− const { props, state } = this
170− sendSignedTransaction(props.web3.value, state.stx)
171− .then(sent => {
172− props.setTxnHashCursor(sent)
173− props.popRoute()
174− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
175− })
176− .catch(err => {
177− this.setState({ txError: err.map(val => val.merge()) })
178− })
179 }
28 }
180
29
181 createUnsignedTxn() {
30 createUnsignedTxn() {
@@ -204,7 +53,6 @@ class CancelTransfer extends React.Component {
204 return Maybe.Just(txn)
53 return Maybe.Just(txn)
205 }
54 }
206
55
207−
208 render() {
56 render() {
209 const { props, state } = this
57 const { props, state } = this
210
58
@@ -217,10 +65,6 @@ class CancelTransfer extends React.Component {
217 // const canGenerate = validAddress === true
65 // const canGenerate = validAddress === true
218
66
219 const canGenerate = true
67 const canGenerate = true
220− const canSign = Maybe.Just.hasInstance(state.txn)
221− const canApprove = Maybe.Just.hasInstance(state.stx)
222− const canSend = Maybe.Just.hasInstance(state.stx) && state.userApproval === true
223−
224
68
225 return (
69 return (
226 <Row>
70 <Row>
@@ -242,46 +86,12 @@ class CancelTransfer extends React.Component {
242 walletType={props.walletType}
86 walletType={props.walletType}
243 walletHdPath={props.walletHdPath}
87 walletHdPath={props.walletHdPath}
244 networkType={props.networkType}
88 networkType={props.networkType}
245− // Tx
89+ setTxnHashCursor={props.setTxnHashCursor}
246− txn={state.txn}
90+ popRoute={props.popRoute}
247− stx={state.stx}
91+ pushRoute={props.pushRoute}
248− // Tx details
92+ // Other
249− nonce={state.nonce}
250− gasPrice={state.gasPrice}
251− chainId={state.chainId}
252− gasLimit={state.gasLimit}
253− showGasDetails={state.showGasDetails}
254− toggleGasDetails={this.toggleGasDetails}
255− // Checks
256− userApproval={state.userApproval}
257 canGenerate={ canGenerate }
93 canGenerate={ canGenerate }
258− canSign={ canSign }
259− canApprove={ canApprove }
260− canSend={ canSend }
261− // Methods
262− createUnsignedTxn={this.handleCreateUnsignedTxn}
94+ createUnsignedTxn={this.createUnsignedTxn} />
263− setUserApproval={this.handleSetUserApproval}
264− setTxn={this.handleSetTxn}
265− setStx={this.handleSetStx}
266− setNonce={this.handleSetNonce}
267− setChainId={this.handleSetChainId}
268− setGasPrice={this.handleSetGasPrice}
269− setGasLimit={this.handleSetGasLimit}
270− handleSubmit={this.handleSubmit} />
271−
272− {
273− Maybe.Nothing.hasInstance(state.txError)
274− ? ''
275− : <Warning className={'mt-8'}>
276− <H3 style={{marginTop: 0, paddingTop: 0}}>
277− {
278− 'There was an error sending your transaction.'
279− }
280− </H3>
281− { state.txError.value }
282− </Warning>
283− }
284−
285 </Col>
95 </Col>
286 </Row>
96 </Row>
287 )
97 )
src/bridge/views/CreateGalaxy.js+18−96
@@ -9,16 +9,8 @@ import { InnerLabel, GalaxyInput, AddressInput, ValidatedSigil, ShowBlockie } fr
9import StatelessTransaction from '../components/StatelessTransaction'
9import StatelessTransaction from '../components/StatelessTransaction'
10
10
11import { BRIDGE_ERROR } from '../lib/error'
11import { BRIDGE_ERROR } from '../lib/error'
12−
13import { NETWORK_NAMES } from '../lib/network'
12import { NETWORK_NAMES } from '../lib/network'
14−
15−import {
16− sendSignedTransaction,
17− getTxnInfo,
18− canDecodePatp
13+import { canDecodePatp } from '../lib/txn'
19−} from '../lib/txn'
20−
21−import { ROUTE_NAMES } from '../lib/router'
22
14
23import {
15import {
24 addressFromSecp256k1Public,
16 addressFromSecp256k1Public,
@@ -61,54 +53,23 @@ class CreateGalaxy extends React.Component {
61 galaxyOwner: galaxyOwner,
53 galaxyOwner: galaxyOwner,
62 galaxyName: '',
54 galaxyName: '',
63 isAvailable: null,
55 isAvailable: null,
64− txApproval: false,
65− nonce: '',
66− gasPrice: '5',
67− showGasDetails: false,
68− chainId: '',
69− txInfo: '',
70− gasLimit: '600000',
71− txn: Maybe.Nothing(),
72− stx: Maybe.Nothing(),
73 }
56 }
74
57
75 this.handleAddressInput = this.handleAddressInput.bind(this)
58 this.handleAddressInput = this.handleAddressInput.bind(this)
76 this.handleGalaxyNameInput = this.handleGalaxyNameInput.bind(this)
59 this.handleGalaxyNameInput = this.handleGalaxyNameInput.bind(this)
77 this.confirmAvailability = this.confirmAvailability.bind(this)
60 this.confirmAvailability = this.confirmAvailability.bind(this)
78− this.handleSubmit = this.handleSubmit.bind(this)
61+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
79− this.toggleGasDetails = this.toggleGasDetails.bind(this)
62+ this.statelessRef = React.createRef();
80− }
81−
82− toggleGasDetails() {
83− this.setState({
84− showGasDetails: !this.state.showGasDetails
85− })
86 }
63 }
87
64
88− componentDidMount() {
89− const { props } = this
90− const addr = props.wallet.matchWith({
91− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
92− Nothing: () => {
93− throw BRIDGE_ERROR.MISSING_WALLET
94− }
95− })
96−
97− // getTxnInfo(props.web3.value, addr).then(txInfo => this.setState(txInfo))
98− props.web3.map(w3 => getTxnInfo(w3, addr)
99− .then(txInfo => this.setState(txInfo))
100− )
101− }
102−
103−
104 handleGalaxyNameInput = (galaxyName) => {
65 handleGalaxyNameInput = (galaxyName) => {
105 this.setState({ galaxyName, isAvailable: null })
66 this.setState({ galaxyName, isAvailable: null })
106− this.clearTransaction()
67+ this.statelessRef.current.clearTxn()
107 }
68 }
108
69
109 handleAddressInput = (galaxyOwner) => {
70 handleAddressInput = (galaxyOwner) => {
110 this.setState({ galaxyOwner })
71 this.setState({ galaxyOwner })
111− this.clearTransaction()
72+ this.statelessRef.current.clearTxn()
112 }
73 }
113
74
114 createUnsignedTxn = () => {
75 createUnsignedTxn = () => {
@@ -133,18 +94,7 @@ class CreateGalaxy extends React.Component {
133 state.galaxyOwner
94 state.galaxyOwner
134 )
95 )
135
96
136− this.setState({ txn: Maybe.Just(txn) })
97+ return Maybe.Just(txn)
137− }
138−
139−
140− handleSubmit = () => {
141− const { props, state } = this
142− sendSignedTransaction(props.web3.value, state.stx)
143− .then(sent => {
144− props.setTxnHashCursor(sent)
145− props.popRoute()
146− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
147− })
148 }
98 }
149
99
150 confirmAvailability = async () => {
100 confirmAvailability = async () => {
@@ -174,21 +124,16 @@ class CreateGalaxy extends React.Component {
174 this.setState({ isAvailable: available })
124 this.setState({ isAvailable: available })
175 }
125 }
176
126
177−
178− clearTransaction = () => {
179− this.setState({
180− txApproval: false,
181− txn: Maybe.Nothing(),
182− stx: Maybe.Nothing(),
183− })
184− }
185−
186 render() {
127 render() {
187 const { props, state } = this
128 const { props, state } = this
188
129
189 const validAddress = isValidAddress(state.galaxyOwner)
130 const validAddress = isValidAddress(state.galaxyOwner)
190 const validGalaxy = isValidGalaxy(state.galaxyName)
131 const validGalaxy = isValidGalaxy(state.galaxyName)
191
132
133+ const canGenerate = validAddress === true &&
134+ validGalaxy === true &&
135+ state.isAvailable === true
136+
192 const esvisible =
137 const esvisible =
193 props.networkType === NETWORK_NAMES.ROPSTEN ||
138 props.networkType === NETWORK_NAMES.ROPSTEN ||
194 props.networkType === NETWORK_NAMES.MAINNET
139 props.networkType === NETWORK_NAMES.MAINNET
@@ -259,43 +204,20 @@ class CreateGalaxy extends React.Component {
259 </Button>
204 </Button>
260
205
261 <StatelessTransaction
206 <StatelessTransaction
262− address={state.galaxyOwner}
207+ // Upper scope
263− shipName={state.galaxyName}
264− isAvailable={state.isAvailable}
265 web3={props.web3}
208 web3={props.web3}
266 contracts={props.contracts}
209 contracts={props.contracts}
267 wallet={props.wallet}
210 wallet={props.wallet}
268 walletType={props.walletType}
211 walletType={props.walletType}
269 walletHdPath={props.walletHdPath}
212 walletHdPath={props.walletHdPath}
270 networkType={props.networkType}
213 networkType={props.networkType}
271− // Tx
214+ setTxnHashCursor={props.setTxnHashCursor}
272− txApproval={state.txApproval}
215+ popRoute={props.popRoute}
273− txn={state.txn}
216+ pushRoute={props.pushRoute}
274− stx={state.stx}
217+ // Other
275− nonce={state.nonce}
218+ ref={this.statelessRef}
276− gasPrice={state.gasPrice}
277− showGasDetails={state.showGasDetails}
278− toggleGasDetails={this.toggleGasDetails}
279− chainId={state.chainId}
280− gasLimit={state.gasLimit}
281− canGenerate={
219+ canGenerate={canGenerate}
282− validAddress === true &&
283− validGalaxy === true &&
284− state.isAvailable === true
285− }
286− canSign={Maybe.Just.hasInstance(state.txn)}
287− canSend={Maybe.Just.hasInstance(state.stx)}
288− // Methods
289− createUnsignedTxn={() => this.createUnsignedTxn()}
220+ createUnsignedTxn={this.createUnsignedTxn} />
290− clearTransaction={() => this.clearTransaction()}
291− setApproval={() => this.setState({txApproval: !state.txApproval})}
292− setTxn={txn => this.setState({txn})}
293− setStx={stx => this.setState({stx})}
294− setNonce={nonce => this.setState({nonce})}
295− setChainId={chainId => this.setState({chainId})}
296− setGasPrice={gasPrice => this.setState({gasPrice})}
297− setGasLimit={gasLimit => this.setState({gasLimit})}
298− handleSubmit={this.handleSubmit} />
299
221
300 </Col>
222 </Col>
301 </Row>
223 </Row>
src/bridge/views/GenKeyfile.js+1−3
@@ -2,9 +2,7 @@ import React from 'react'
2import Maybe from 'folktale/maybe'
2import Maybe from 'folktale/maybe'
3
3
4import { Button } from '../components/Base'
4import { Button } from '../components/Base'
5−import { RequiredInput, InnerLabel } from '../components/Base'
6import { Row, Col, H1, P } from '../components/Base'
5import { Row, Col, H1, P } from '../components/Base'
7−import { Code } from '../components/Base'
8
6
9import * as ob from 'urbit-ob'
7import * as ob from 'urbit-ob'
10import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
8import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
@@ -96,7 +94,7 @@ class GenKeyfile extends React.Component {
96 }
94 }
97
95
98 render() {
96 render() {
99− const { point, pointDetails, revision } = this.getPointDetails();
97+ const { point, revision } = this.getPointDetails();
100 const { keyfile, loaded } = this.state
98 const { keyfile, loaded } = this.state
101
99
102 return (
100 return (
src/bridge/views/IssueChild.js+14−225
@@ -3,23 +3,21 @@ import React from 'react'
3import { azimuth, ecliptic } from 'azimuth-js'
3import { azimuth, ecliptic } from 'azimuth-js'
4import * as ob from 'urbit-ob'
4import * as ob from 'urbit-ob'
5
5
6−import { Row, Col, H1, H3, P, Anchor, Warning } from '../components/Base'
6+import { Row, Col, H1, P, Anchor } from '../components/Base'
7import { Button, ShowBlockie, ValidatedSigil } from '../components/Base'
7import { Button, ShowBlockie, ValidatedSigil } from '../components/Base'
8import { PointInput, AddressInput, InnerLabel } from '../components/Base'
8import { PointInput, AddressInput, InnerLabel } from '../components/Base'
9
9
10import StatelessTransaction from '../components/StatelessTransaction'
10import StatelessTransaction from '../components/StatelessTransaction'
11
11
12−import { ROUTE_NAMES } from '../lib/router'
13import { NETWORK_NAMES } from '../lib/network'
12import { NETWORK_NAMES } from '../lib/network'
14import { BRIDGE_ERROR } from '../lib/error'
13import { BRIDGE_ERROR } from '../lib/error'
15import { getSpawnCandidate } from '../lib/child'
14import { getSpawnCandidate } from '../lib/child'
16−import { canDecodePatp, sendSignedTransaction, fromWei } from '../lib/txn'
15+import { canDecodePatp } from '../lib/txn'
17
16
18import {
17import {
19 ETH_ZERO_ADDR,
18 ETH_ZERO_ADDR,
20 isValidAddress,
19 isValidAddress,
21− eqAddr,
20+ eqAddr
22− addressFromSecp256k1Public
23} from '../lib/wallet'
21} from '../lib/wallet'
24
22
25class IssueChild extends React.Component {
23class IssueChild extends React.Component {
@@ -43,100 +41,35 @@ class IssueChild extends React.Component {
43 ]
41 ]
44
42
45 this.state = {
43 this.state = {
46− txError: Nothing(),
47 receivingAddress: '',
44 receivingAddress: '',
48 issuingPoint: issuingPoint,
45 issuingPoint: issuingPoint,
49 desiredPoint: '',
46 desiredPoint: '',
50 isAvailable: Nothing(), // use Nothing to allow attempt when offline
47 isAvailable: Nothing(), // use Nothing to allow attempt when offline
51− userApproval: false,
52− nonce: '',
53− gasPrice: '5',
54− showGasDetails: false,
55− chainId: '',
56− gasLimit: '600000',
57− txn: Nothing(),
58− stx: Nothing(),
59 suggestions: suggestions,
48 suggestions: suggestions,
60 }
49 }
61
50
62 this.handlePointInput = this.handlePointInput.bind(this)
51 this.handlePointInput = this.handlePointInput.bind(this)
63 this.handleAddressInput = this.handleAddressInput.bind(this)
52 this.handleAddressInput = this.handleAddressInput.bind(this)
64 this.handleConfirmAvailability = this.handleConfirmAvailability.bind(this)
53 this.handleConfirmAvailability = this.handleConfirmAvailability.bind(this)
65− this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
54+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
66− this.handleSubmit = this.handleSubmit.bind(this)
55+ this.statelessRef = React.createRef();
67− this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
68− this.handleSetTxn = this.handleSetTxn.bind(this)
69− this.handleSetStx = this.handleSetStx.bind(this)
70− this.handleSetNonce = this.handleSetNonce.bind(this)
71− this.handleSetChainId = this.handleSetChainId.bind(this)
72− this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
73− this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
74− this.toggleGasDetails = this.toggleGasDetails.bind(this)
75 }
56 }
76
57
77− toggleGasDetails() {
78− this.setState({
79− showGasDetails: !this.state.showGasDetails
80− })
81− }
82−
83− componentDidMount() {
84− const { props } = this
85−
86− const addr = props.wallet.matchWith({
87− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
88− Nothing: () => {
89− throw BRIDGE_ERROR.MISSING_WALLET
90− }
91− })
92−
93− props.web3.matchWith({
94− Nothing: () => {},
95− Just: (w3) => {
96− const validWeb3 = w3.value
97−
98− const getTxMetadata = [
99− validWeb3.eth.getTransactionCount(addr),
100− validWeb3.eth.net.getId(),
101− validWeb3.eth.getGasPrice()
102− ]
103−
104− Promise.all(getTxMetadata).then(r => {
105− const txMetadata = {
106− nonce: r[0],
107− chainId: r[1],
108− gasPrice: fromWei(r[2], 'gwei'),
109− }
110−
111− this.setState({...txMetadata})
112−
113− })
114− }
115− })
116−
117− }
118−
119−
120−
121 handleAddressInput = (receivingAddress) => {
58 handleAddressInput = (receivingAddress) => {
122 this.setState({ receivingAddress })
59 this.setState({ receivingAddress })
123− this.handleClearTxn()
60+ this.statelessRef.current.clearTxn()
124 }
61 }
125
62
126−
127−
128 handlePointInput(desiredPoint) {
63 handlePointInput(desiredPoint) {
129 if (desiredPoint.length < 15) {
64 if (desiredPoint.length < 15) {
130 this.setState({
65 this.setState({
131 desiredPoint,
66 desiredPoint,
132 isAvailable: Nothing()
67 isAvailable: Nothing()
133 })
68 })
134− this.handleClearTxn()
69+ this.statelessRef.current.clearTxn()
135 }
70 }
136 }
71 }
137
72
138−
139−
140 handleConfirmAvailability() {
73 handleConfirmAvailability() {
141 this.confirmPointAvailability().then(r => {
74 this.confirmPointAvailability().then(r => {
142 this.setState({
75 this.setState({
@@ -146,106 +79,6 @@ class IssueChild extends React.Component {
146
79
147 }
80 }
148
81
149−
150−
151− handleCreateUnsignedTxn() {
152− const txn = this.createUnsignedTxn()
153− this.setState({ txn })
154− }
155−
156−
157− handleSetUserApproval(){
158− const {state} = this
159− this.setState({ userApproval: !state.userApproval })
160− }
161−
162−
163−
164− handleSetTxn(txn){
165− this.setState({ txn })
166− }
167−
168−
169−
170− handleSetStx(stx){
171− this.setState({
172− stx,
173− userApproval: false,
174− })
175− }
176−
177−
178−
179− handleSetNonce(nonce){
180− this.setState({ nonce })
181− this.handleClearStx()
182− }
183−
184−
185−
186− handleSetChainId(chainId){
187− this.setState({ chainId })
188− this.handleClearStx()
189− }
190−
191−
192−
193− handleSetGasPrice(gasPrice){
194− this.setState({ gasPrice })
195− this.handleClearStx()
196− }
197−
198−
199−
200− handleSetGasLimit(gasLimit){
201− this.setState({ gasLimit })
202− this.handleClearStx()
203− }
204−
205−
206−
207− handleClearStx() {
208− this.setState({
209− userApproval: false,
210− stx: Nothing(),
211− })
212− }
213−
214−
215−
216− handleClearTxn() {
217− this.setState({
218− userApproval: false,
219− txn: Nothing(),
220− stx: Nothing(),
221− })
222− }
223−
224−
225−
226− handleClearTransaction() {
227− this.setState({
228− userApproval: false,
229− txn: Nothing(),
230− stx: Nothing(),
231− })
232− }
233−
234−
235−
236− handleSubmit(){
237− const { props, state } = this
238− sendSignedTransaction(props.web3.value, state.stx)
239− .then(sent => {
240− props.setTxnHashCursor(sent)
241− props.popRoute()
242− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
243− })
244− .catch(err => {
245− this.setState({ txError: err.map(val => val.merge()) })
246− })
247− }
248−
249 async confirmPointAvailability() {
82 async confirmPointAvailability() {
250 const { contracts } = this.props
83 const { contracts } = this.props
251 const { desiredPoint } = this.state
84 const { desiredPoint } = this.state
@@ -307,8 +140,6 @@ class IssueChild extends React.Component {
307 return Just(txn)
140 return Just(txn)
308 }
141 }
309
142
310−
311−
312 buttonTriState() {
143 buttonTriState() {
313 const a = this.state.isAvailable
144 const a = this.state.isAvailable
314 if (Nothing.hasInstance(a)) return 'blue'
145 if (Nothing.hasInstance(a)) return 'blue'
@@ -316,8 +147,6 @@ class IssueChild extends React.Component {
316 if (a.value === true) return 'green'
147 if (a.value === true) return 'green'
317 }
148 }
318
149
319−
320−
321 buttonTriStateText() {
150 buttonTriStateText() {
322 const a = this.state.isAvailable
151 const a = this.state.isAvailable
323 if (Nothing.hasInstance(a)) return 'Confirm Availablility'
152 if (Nothing.hasInstance(a)) return 'Confirm Availablility'
@@ -343,7 +172,6 @@ class IssueChild extends React.Component {
343 return vpatp && vchild
172 return vpatp && vchild
344 }
173 }
345
174
346−
347 render() {
175 render() {
348 const { props, state } = this
176 const { props, state } = this
349
177
@@ -362,12 +190,6 @@ class IssueChild extends React.Component {
362 }
190 }
363 })
191 })
364
192
365− const canSign = Just.hasInstance(state.txn)
366− const canApprove = Just.hasInstance(state.stx)
367− const canSend =
368− Just.hasInstance(state.stx)
369− && state.userApproval === true
370−
371 const esvisible =
193 const esvisible =
372 props.networkType === NETWORK_NAMES.ROPSTEN ||
194 props.networkType === NETWORK_NAMES.ROPSTEN ||
373 props.networkType === NETWORK_NAMES.MAINNET
195 props.networkType === NETWORK_NAMES.MAINNET
@@ -480,46 +302,13 @@ class IssueChild extends React.Component {
480 walletType={props.walletType}
302 walletType={props.walletType}
481 walletHdPath={props.walletHdPath}
303 walletHdPath={props.walletHdPath}
482 networkType={props.networkType}
304 networkType={props.networkType}
483− // Tx
305+ setTxnHashCursor={props.setTxnHashCursor}
484− txn={state.txn}
306+ popRoute={props.popRoute}
485− stx={state.stx}
307+ pushRoute={props.pushRoute}
486− // Tx details
308+ // Other
487− nonce={state.nonce}
488− gasPrice={state.gasPrice}
489− showGasDetails={state.showGasDetails}
490− toggleGasDetails={this.toggleGasDetails}
491− chainId={state.chainId}
492− gasLimit={state.gasLimit}
493− // Checks
494− userApproval={state.userApproval}
495− canGenerate={ canGenerate }
309+ canGenerate={canGenerate}
496− canSign={ canSign }
497− canApprove={ canApprove }
498− canSend={ canSend }
499− // Methods
500− createUnsignedTxn={this.handleCreateUnsignedTxn}
310+ createUnsignedTxn={this.createUnsignedTxn}
501− setUserApproval={this.handleSetUserApproval}
311+ ref={this.statelessRef} />
502− setTxn={this.handleSetTxn}
503− setStx={this.handleSetStx}
504− setNonce={this.handleSetNonce}
505− setChainId={this.handleSetChainId}
506− setGasPrice={this.handleSetGasPrice}
507− setGasLimit={this.handleSetGasLimit}
508− handleSubmit={this.handleSubmit} />
509−
510− {
511− Nothing.hasInstance(state.txError)
512− ? ''
513− : <Warning className={'mt-8'}>
514− <H3 style={{marginTop: 0, paddingTop: 0}}>
515− {
516− 'There was an error sending your transaction.'
517− }
518− </H3>
519− { state.txError.value }
520− </Warning>
521− }
522−
523 </Col>
312 </Col>
524 </Row>
313 </Row>
525 )
314 )
src/bridge/views/Mnemonic.js+53−11
@@ -1,8 +1,13 @@
1import * as bip39 from 'bip39'
1import * as bip39 from 'bip39'
2−import Maybe from 'folktale/maybe'
2+import { Just, Nothing } from 'folktale/maybe'
3import React from 'react'
3import React from 'react'
4import { Button } from '../components/Base'
4import { Button } from '../components/Base'
5+import {
6+ Input,
7+ MnemonicInput,
8+ InnerLabel,
9+ InputCaption
5−import { Input, MnemonicInput, InnerLabel, InputCaption } from '../components/Base'
10+ } from '../components/Base'
6import { Row, Col, H1 } from '../components/Base'
11import { Row, Col, H1 } from '../components/Base'
7
12
8import { ROUTE_NAMES } from '../lib/router'
13import { ROUTE_NAMES } from '../lib/router'
@@ -16,28 +21,47 @@ class Mnemonic extends React.Component {
16
21
17 this.state = {
22 this.state = {
18 mnemonic: '',
23 mnemonic: '',
24+ passphrase: '',
19 hdpath: DEFAULT_HD_PATH,
25 hdpath: DEFAULT_HD_PATH,
20 }
26 }
21
27
22 this.handleMnemonicInput = this.handleMnemonicInput.bind(this)
28 this.handleMnemonicInput = this.handleMnemonicInput.bind(this)
29+ this.handlePassphraseInput = this.handlePassphraseInput.bind(this)
23 this.handleHdPathInput = this.handleHdPathInput.bind(this)
30 this.handleHdPathInput = this.handleHdPathInput.bind(this)
24 }
31 }
25
32
26 componentDidMount() {
33 componentDidMount() {
27− const { mnemonic, hdpath } = this.state
34+ const { mnemonic, hdpath, passphrase } = this.state
28− this.attemptWalletDerivation(mnemonic, hdpath)
35+ this.attemptWalletDerivation(mnemonic, hdpath, passphrase)
29
36
30 this.setState({
37 this.setState({
31 exampleMnemonic: bip39.generateMnemonic(),
38 exampleMnemonic: bip39.generateMnemonic(),
32 })
39 })
33 }
40 }
34
41
42+ handlePassphraseInput(passphrase) {
43+ this.setState((state, _) => {
44+ const mnemonic = state.mnemonic
45+ const hdpath = state.hdpath
46+ this.attemptWalletDerivation(
47+ mnemonic,
48+ hdpath === '' ? DEFAULT_HD_PATH : hdpath,
49+ passphrase
50+ )
51+ return {
52+ passphrase
53+ }
54+ })
55+ }
56+
35 handleMnemonicInput(mnemonic) {
57 handleMnemonicInput(mnemonic) {
36 this.setState((state, _) => {
58 this.setState((state, _) => {
37 const hdpath = state.hdpath
59 const hdpath = state.hdpath
60+ const passphrase = state.passphrase
38 this.attemptWalletDerivation(
61 this.attemptWalletDerivation(
39 mnemonic,
62 mnemonic,
40− hdpath === '' ? DEFAULT_HD_PATH : hdpath
63+ hdpath === '' ? DEFAULT_HD_PATH : hdpath,
64+ passphrase
41 )
65 )
42 return {
66 return {
43 mnemonic
67 mnemonic
@@ -48,9 +72,11 @@ class Mnemonic extends React.Component {
48 handleHdPathInput(hdpath) {
72 handleHdPathInput(hdpath) {
49 this.setState((state, _) => {
73 this.setState((state, _) => {
50 const mnemonic = state.mnemonic
74 const mnemonic = state.mnemonic
75+ const passphrase = state.passphrase
51 this.attemptWalletDerivation(
76 this.attemptWalletDerivation(
52 mnemonic,
77 mnemonic,
53− hdpath === '' ? DEFAULT_HD_PATH : hdpath
78+ hdpath === '' ? DEFAULT_HD_PATH : hdpath,
79+ passphrase
54 )
80 )
55 return {
81 return {
56 hdpath
82 hdpath
@@ -58,11 +84,11 @@ class Mnemonic extends React.Component {
58 })
84 })
59 }
85 }
60
86
61− attemptWalletDerivation(mnemonic, hdpath) {
87+ attemptWalletDerivation(mnemonic, hdpath, passphrase) {
62 const { setWallet, setAuthMnemonic, setWalletHdPath } = this.props
88 const { setWallet, setAuthMnemonic, setWalletHdPath } = this.props
63− const wallet = walletFromMnemonic(mnemonic, hdpath)
89+ const wallet = walletFromMnemonic(mnemonic, hdpath, passphrase)
64 setWallet(wallet)
90 setWallet(wallet)
65− setAuthMnemonic(Maybe.Just(mnemonic))
91+ setAuthMnemonic(Just(mnemonic))
66 setWalletHdPath(hdpath)
92 setWalletHdPath(hdpath)
67 }
93 }
68
94
@@ -71,7 +97,8 @@ class Mnemonic extends React.Component {
71 const {
97 const {
72 mnemonic,
98 mnemonic,
73 hdpath,
99 hdpath,
74− exampleMnemonic
100+ exampleMnemonic,
101+ passphrase
75 } = this.state
102 } = this.state
76
103
77 return (
104 return (
@@ -96,6 +123,21 @@ class Mnemonic extends React.Component {
96 <InnerLabel>{'Mnemonic'}</InnerLabel>
123 <InnerLabel>{'Mnemonic'}</InnerLabel>
97 </MnemonicInput>
124 </MnemonicInput>
98
125
126+ <InputCaption>
127+ {`If your wallet requires a passphrase, you may enter it below.`}
128+ </InputCaption>
129+
130+ <Input
131+ className='pt-8'
132+ prop-size='md'
133+ prop-format='innerLabel'
134+ name='passphrase'
135+ type='password'
136+ value={ passphrase }
137+ autocomplete='off'
138+ onChange={ this.handlePassphraseInput }>
139+ <InnerLabel>{'Passphrase'}</InnerLabel>
140+ </Input>
99
141
100 <InputCaption>
142 <InputCaption>
101 {`If you'd like to use a custom derivation path, you may enter it below.`}
143 {`If you'd like to use a custom derivation path, you may enter it below.`}
@@ -114,7 +156,7 @@ class Mnemonic extends React.Component {
114
156
115 <Button
157 <Button
116 className={'mt-10'}
158 className={'mt-10'}
117− disabled={ Maybe.Nothing.hasInstance(wallet) }
159+ disabled={ Nothing.hasInstance(wallet) }
118 onClick={ () => {
160 onClick={ () => {
119 popRoute()
161 popRoute()
120 pushRoute(ROUTE_NAMES.SHIPS)
162 pushRoute(ROUTE_NAMES.SHIPS)
src/bridge/views/Point/Actions.js−7
@@ -24,19 +24,12 @@ const Actions = (props) => {
24 }
24 }
25 })
25 })
26
26
27− const canSetTransferProxy = pointDetails.matchWith({
28− // also should check is addr is operator
29− Nothing: _ => false,
30− Just: details => eqAddr(details.value.owner, addr)
31− })
32−
33 const canSetSpawnProxy = pointDetails.matchWith({
27 const canSetSpawnProxy = pointDetails.matchWith({
34 Nothing: _ => false,
28 Nothing: _ => false,
35 Just: details =>
29 Just: details =>
36 eqAddr(details.value.owner, addr) && details.value.active
30 eqAddr(details.value.owner, addr) && details.value.active
37 })
31 })
38
32
39−
40 const canSetManagementProxy = pointDetails.matchWith({
33 const canSetManagementProxy = pointDetails.matchWith({
41 Nothing: _ => false,
34 Nothing: _ => false,
42 Just: details =>
35 Just: details =>
src/bridge/views/SetKeys.js+9−226
@@ -2,25 +2,14 @@ import React from 'react'
2import Maybe from 'folktale/maybe'
2import Maybe from 'folktale/maybe'
3import * as azimuth from 'azimuth-js'
3import * as azimuth from 'azimuth-js'
4import * as ob from 'urbit-ob'
4import * as ob from 'urbit-ob'
5−import { Row, Col, H1, P, Warning, H3 } from '../components/Base'
5+import { Row, Col, H1, P, Warning } from '../components/Base'
6−import { RequiredInput, InnerLabel } from '../components/Base'
7
6
8import StatelessTransaction from '../components/StatelessTransaction'
7import StatelessTransaction from '../components/StatelessTransaction'
9import { BRIDGE_ERROR } from '../lib/error'
8import { BRIDGE_ERROR } from '../lib/error'
10−import { ROUTE_NAMES } from '../lib/router'
11−import { attemptSeedDerivation, genKey } from '../lib/keys'
9+import { attemptSeedDerivation } from '../lib/keys'
12−
13−import saveAs from 'file-saver'
14−
15−import { WALLET_NAMES } from '../lib/wallet'
16
10
17import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
11import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
18
12
19−import {
20− sendSignedTransaction,
21− fromWei,
22− } from '../lib/txn'
23−
24import {
13import {
25 addressFromSecp256k1Public,
14 addressFromSecp256k1Public,
26 addHexPrefix
15 addHexPrefix
@@ -45,36 +34,12 @@ class SetKeys extends React.Component {
45 point: point,
34 point: point,
46 cryptoSuiteVersion: 1,
35 cryptoSuiteVersion: 1,
47 continuity: false,
36 continuity: false,
48− txn: Maybe.Nothing(),
49− txError: Maybe.Nothing(),
50 isManagementSeed: false,
37 isManagementSeed: false,
51− userApproval: false,
52− nonce: '',
53− gasPrice: '5',
54− showGasDetails: false,
55− chainId: '',
56− gasLimit: '600000',
57− stx: Maybe.Nothing(),
58 }
38 }
59
39
60 this.handleNetworkSeedInput = this.handleNetworkSeedInput.bind(this)
40 this.handleNetworkSeedInput = this.handleNetworkSeedInput.bind(this)
61 // Transaction
41 // Transaction
62− this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
42+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
63− this.handleSubmit = this.handleSubmit.bind(this)
64− this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
65− this.handleSetTxn = this.handleSetTxn.bind(this)
66− this.handleSetStx = this.handleSetStx.bind(this)
67− this.handleSetNonce = this.handleSetNonce.bind(this)
68− this.handleSetChainId = this.handleSetChainId.bind(this)
69− this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
70− this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
71− this.toggleGasDetails = this.toggleGasDetails.bind(this)
72− }
73−
74− toggleGasDetails() {
75− this.setState({
76− showGasDetails: !this.state.showGasDetails
77− })
78 }
43 }
79
44
80 componentDidMount() {
45 componentDidMount() {
@@ -90,30 +55,6 @@ class SetKeys extends React.Component {
90 })
55 })
91
56
92 this.determineManagementSeed(props.contracts.value, addr)
57 this.determineManagementSeed(props.contracts.value, addr)
93−
94− props.web3.matchWith({
95− Nothing: () => {},
96− Just: (w3) => {
97− const validWeb3 = w3.value
98−
99− const getTxMetadata = [
100− validWeb3.eth.getTransactionCount(addr),
101− validWeb3.eth.net.getId(),
102− validWeb3.eth.getGasPrice()
103− ];
104−
105− Promise.all(getTxMetadata).then(r => {
106− const txMetadata = {
107− nonce: r[0],
108− chainId: r[1],
109− gasPrice: fromWei(r[2], 'gwei'),
110− };
111−
112− this.setState({...txMetadata})
113−
114− })
115− }
116− });
117 }
58 }
118
59
119 async determineManagementSeed(ctrcs, addr) {
60 async determineManagementSeed(ctrcs, addr) {
@@ -157,126 +98,6 @@ class SetKeys extends React.Component {
157 this.setState({ networkSeed })
98 this.setState({ networkSeed })
158 }
99 }
159
100
160− handleCreateUnsignedTxn() {
161− const txn = this.createUnsignedTxn()
162− this.setState({ txn })
163− }
164−
165−
166− handleSetUserApproval(){
167− const {state} = this
168− this.setState({ userApproval: !state.userApproval })
169− }
170−
171−
172−
173− handleSetTxn(txn){
174− this.setState({ txn })
175− }
176−
177−
178−
179− handleSetStx(stx){
180− this.setState({
181− stx,
182− userApproval: false,
183− })
184− }
185−
186−
187−
188− handleSetNonce(nonce){
189− this.setState({ nonce })
190− this.handleClearStx()
191− }
192−
193−
194−
195− handleSetChainId(chainId){
196− this.setState({ chainId })
197− this.handleClearStx()
198− }
199−
200−
201−
202− handleSetGasPrice(gasPrice){
203− this.setState({ gasPrice })
204− this.handleClearStx()
205− }
206−
207−
208−
209− handleSetGasLimit(gasLimit){
210− this.setState({ gasLimit })
211− this.handleClearStx()
212− }
213−
214−
215−
216− handleClearStx() {
217− this.setState({
218− userApproval: false,
219− stx: Maybe.Nothing(),
220− })
221− }
222−
223−
224−
225−
226− handleClearTxn() {
227− this.setState({
228− userApproval: false,
229− txn: Maybe.Nothing(),
230− stx: Maybe.Nothing(),
231− })
232− }
233−
234−
235−
236− handleClearTransaction() {
237− this.setState({
238− userApproval: false,
239− txn: Maybe.Nothing(),
240− stx: Maybe.Nothing(),
241− })
242− }
243−
244− downloadKeyfile(networkSeed) {
245− const { pointCache } = this.props
246− const { pointCursor } = this.props
247−
248− const point = pointCursor.matchWith({
249− Just: (pt) => pt.value,
250− Nothing: () => {
251− throw BRIDGE_ERROR.MISSING_POINT
252− }
253− })
254−
255− const pointDetails =
256− point in pointCache
257− ? pointCache[point]
258− : (() => { throw BRIDGE_ERROR.MISSING_POINT })()
259−
260− const revision = parseInt(pointDetails.keyRevisionNumber)
261− const keyfile = genKey(networkSeed, point, revision)
262− let blob = new Blob([keyfile], {type:"text/plain;charset=utf-8"});
263− saveAs(blob, `${ob.patp(point).slice(1)}-${revision}.key`)
264− }
265−
266− handleSubmit(){
267− const { props, state } = this
268− sendSignedTransaction(props.web3.value, state.stx)
269− .then(sent => {
270− props.setNetworkSeedCache(this.state.networkSeed)
271− props.setTxnHashCursor(sent)
272− props.popRoute()
273− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, {promptKeyfile: true})
274− })
275− .catch(err => {
276− this.setState({ txError: err.map(val => val.merge()) })
277− })
278− }
279−
280 createUnsignedTxn() {
101 createUnsignedTxn() {
281 const { state, props } = this
102 const { state, props } = this
282
103
@@ -325,13 +146,6 @@ class SetKeys extends React.Component {
325 state.networkSeed.length === 64
146 state.networkSeed.length === 64
326 && state.networkSeed.length === 64
147 && state.networkSeed.length === 64
327
148
328− const canSign = Maybe.Just.hasInstance(state.txn)
329− const canApprove = Maybe.Just.hasInstance(state.stx)
330−
331− const canSend =
332− Maybe.Just.hasInstance(state.stx)
333− && state.userApproval === true
334−
335 const pointDetails =
149 const pointDetails =
336 state.point in props.pointCache
150 state.point in props.pointCache
337 ? props.pointCache[state.point]
151 ? props.pointCache[state.point]
@@ -379,45 +193,14 @@ class SetKeys extends React.Component {
379 walletType={props.walletType}
193 walletType={props.walletType}
380 walletHdPath={props.walletHdPath}
194 walletHdPath={props.walletHdPath}
381 networkType={props.networkType}
195 networkType={props.networkType}
196+ setTxnHashCursor={props.setTxnHashCursor}
197+ popRoute={props.popRoute}
198+ pushRoute={props.pushRoute}
382 // Tx
199 // Tx
383− txn={state.txn}
384− stx={state.stx}
385− // Tx details
386− nonce={state.nonce}
387− gasPrice={state.gasPrice}
388− chainId={state.chainId}
389− gasLimit={state.gasLimit}
390− showGasDetails={state.showGasDetails}
391− toggleGasDetails={this.toggleGasDetails}
392− // Checks
393− userApproval={state.userApproval}
394 canGenerate={ canGenerate }
200 canGenerate={ canGenerate }
395− canSign={ canSign }
396− canApprove={ canApprove }
397− canSend={ canSend }
398− // Methods
399− createUnsignedTxn={this.handleCreateUnsignedTxn}
201+ createUnsignedTxn={this.createUnsignedTxn}
400− setUserApproval={this.handleSetUserApproval}
202+ networkSeed={ state.networkSeed }
401− setTxn={this.handleSetTxn}
203+ setNetworkSeedCache={ props.setNetworkSeedCache } />
402− setStx={this.handleSetStx}
403− setNonce={this.handleSetNonce}
404− setChainId={this.handleSetChainId}
405− setGasPrice={this.handleSetGasPrice}
406− setGasLimit={this.handleSetGasLimit}
407− handleSubmit={this.handleSubmit} />
408−
409− {
410− Maybe.Nothing.hasInstance(state.txError)
411− ? ''
412− : <Warning className={'mt-8'}>
413− <H3 style={{marginTop: 0, paddingTop: 0}}>
414− {
415− 'There was an error sending your transaction.'
416− }
417− </H3>
418− { state.txError.value }
419− </Warning>
420− }
421
204
422 </Col>
205 </Col>
423 </Row>
206 </Row>
src/bridge/views/SetProxy.js+13−224
@@ -1,8 +1,8 @@
1import Maybe from 'folktale/maybe'
1import Maybe from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3import * as azimuth from 'azimuth-js'
3import * as azimuth from 'azimuth-js'
4−import { Row, Col, H1, H3, P, InnerLabel, ShowBlockie, Anchor } from '../components/Base'
4+import { Row, Col, H1, P, InnerLabel, ShowBlockie, Anchor } from '../components/Base'
5−import { AddressInput, Warning } from '../components/Base'
5+import { AddressInput } from '../components/Base'
6import * as ob from 'urbit-ob'
6import * as ob from 'urbit-ob'
7
7
8import { PROXY_TYPE, renderProxyType } from '../lib/proxy'
8import { PROXY_TYPE, renderProxyType } from '../lib/proxy'
@@ -12,17 +12,7 @@ import { NETWORK_NAMES } from '../lib/network'
12
12
13import StatelessTransaction from '../components/StatelessTransaction'
13import StatelessTransaction from '../components/StatelessTransaction'
14
14
15−import { ROUTE_NAMES } from '../lib/router'
16−
17−import {
18− sendSignedTransaction,
19− fromWei,
20− } from '../lib/txn'
21−
22−import {
23− isValidAddress,
15+import { isValidAddress } from '../lib/wallet'
24− addressFromSecp256k1Public,
25−} from '../lib/wallet'
26
16
27const SetManagementProxy = (props) =>
17const SetManagementProxy = (props) =>
28 <SetProxy
18 <SetProxy
@@ -55,181 +45,20 @@ class SetProxy extends React.Component {
55
45
56 this.state = {
46 this.state = {
57 proxyAddress: '',
47 proxyAddress: '',
58− txn: Maybe.Nothing(),
59− txError: Maybe.Nothing(),
60 issuingPoint: issuingPoint,
48 issuingPoint: issuingPoint,
61− showGasDetails: false,
62− userApproval: false,
63− nonce: '',
64− gasPrice: '5',
65− chainId: '',
66− gasLimit: '600000',
67− stx: Maybe.Nothing(),
68 }
49 }
69
50
51+ this.statelessRef = React.createRef();
70 this.handleAddressInput = this.handleAddressInput.bind(this)
52 this.handleAddressInput = this.handleAddressInput.bind(this)
71− // Transaction
53+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
72− this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
73− this.handleSubmit = this.handleSubmit.bind(this)
74− this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
75− this.handleSetTxn = this.handleSetTxn.bind(this)
76− this.handleSetStx = this.handleSetStx.bind(this)
77− this.handleSetNonce = this.handleSetNonce.bind(this)
78− this.handleSetChainId = this.handleSetChainId.bind(this)
79− this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
80− this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
81− this.toggleGasDetails = this.toggleGasDetails.bind(this)
82− }
83−
84− toggleGasDetails() {
85− this.setState({
86− showGasDetails: !this.state.showGasDetails
87− })
88 }
54 }
89
55
90− componentDidMount() {
91− const { props } = this
92−
93− const addr = props.wallet.matchWith({
94− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
95− Nothing: () => {
96− throw BRIDGE_ERROR.MISSING_WALLET
97− }
98− })
99−
100− props.web3.matchWith({
101− Nothing: () => {},
102− Just: (w3) => {
103− const validWeb3 = w3.value
104−
105− const getTxMetadata = [
106− validWeb3.eth.getTransactionCount(addr),
107− validWeb3.eth.net.getId(),
108− validWeb3.eth.getGasPrice()
109− ];
110−
111− Promise.all(getTxMetadata).then(r => {
112− const txMetadata = {
113− nonce: r[0],
114− chainId: r[1],
115− gasPrice: fromWei(r[2], 'gwei'),
116− };
117−
118− this.setState({...txMetadata})
119−
120− })
121− }
122− });
123−
124− }
125−
126−
127−
128 handleAddressInput(proxyAddress) {
56 handleAddressInput(proxyAddress) {
129 this.setState({ proxyAddress })
57 this.setState({ proxyAddress })
130− this.handleClearTxn()
131− }
132−
133−
134− handleCreateUnsignedTxn() {
135− const txn = this.createUnsignedTxn()
136− this.setState({ txn })
137− }
138−
139−
140− handleSetUserApproval(){
141− const {state} = this
142− this.setState({ userApproval: !state.userApproval })
143− }
144−
145−
146−
147− handleSetTxn(txn){
148− this.setState({ txn })
149− }
150−
151−
152−
153− handleSetStx(stx){
154− this.setState({
155− stx,
156− userApproval: false,
157− })
158− }
159−
160−
161
58
162− handleSetNonce(nonce){
59+ this.statelessRef.current.clearTxn()
163− this.setState({ nonce })
164− this.handleClearStx()
165 }
60 }
166
61
167−
168−
169− handleSetChainId(chainId){
170− this.setState({ chainId })
171− this.handleClearStx()
172− }
173−
174−
175−
176− handleSetGasPrice(gasPrice){
177− this.setState({ gasPrice })
178− this.handleClearStx()
179− }
180−
181−
182−
183− handleSetGasLimit(gasLimit){
184− this.setState({ gasLimit })
185− this.handleClearStx()
186− }
187−
188−
189−
190− handleClearStx() {
191− this.setState({
192− userApproval: false,
193− stx: Maybe.Nothing(),
194− })
195− }
196−
197−
198−
199− handleClearTxn() {
200− this.setState({
201− userApproval: false,
202− txn: Maybe.Nothing(),
203− stx: Maybe.Nothing(),
204− })
205− }
206−
207−
208−
209− handleClearTransaction() {
210− this.setState({
211− userApproval: false,
212− txn: Maybe.Nothing(),
213− stx: Maybe.Nothing(),
214− })
215− }
216−
217−
218−
219− handleSubmit(){
220− const { props, state } = this
221− sendSignedTransaction(props.web3.value, state.stx)
222− .then(sent => {
223− props.setTxnHashCursor(sent)
224− props.popRoute()
225− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
226− })
227− .catch(err => {
228− this.setState({ txError: err.map(val => val.merge()) })
229− })
230− }
231−
232−
233 createUnsignedTxn(proxyAddress) {
62 createUnsignedTxn(proxyAddress) {
234 const { state, props } = this
63 const { state, props } = this
235
64
@@ -270,19 +99,12 @@ class SetProxy extends React.Component {
270 }
99 }
271
100
272 render() {
101 render() {
273−
274 const { props, state } = this
102 const { props, state } = this
275
103
276 const renderedProxyType = renderProxyType(props.proxyType)
104 const renderedProxyType = renderProxyType(props.proxyType)
277−
278 const validAddress = isValidAddress(state.proxyAddress)
105 const validAddress = isValidAddress(state.proxyAddress)
279−
280 const canGenerate = validAddress === true
106 const canGenerate = validAddress === true
281
107
282− const canSign = Maybe.Just.hasInstance(state.txn)
283− const canApprove = Maybe.Just.hasInstance(state.stx)
284− const canSend = Maybe.Just.hasInstance(state.stx) && state.userApproval === true
285−
286 const esvisible =
108 const esvisible =
287 props.networkType === NETWORK_NAMES.ROPSTEN ||
109 props.networkType === NETWORK_NAMES.ROPSTEN ||
288 props.networkType === NETWORK_NAMES.MAINNET
110 props.networkType === NETWORK_NAMES.MAINNET
@@ -339,46 +161,13 @@ class SetProxy extends React.Component {
339 walletType={props.walletType}
161 walletType={props.walletType}
340 walletHdPath={props.walletHdPath}
162 walletHdPath={props.walletHdPath}
341 networkType={props.networkType}
163 networkType={props.networkType}
342− // Tx
164+ setTxnHashCursor={props.setTxnHashCursor}
343− txn={state.txn}
165+ popRoute={props.popRoute}
344− stx={state.stx}
166+ pushRoute={props.pushRoute}
345− // Tx details
167+ // Other
346− nonce={state.nonce}
168+ ref={this.statelessRef}
347− gasPrice={state.gasPrice}
169+ createUnsignedTxn={this.createUnsignedTxn}
348− chainId={state.chainId}
349− gasLimit={state.gasLimit}
350− showGasDetails={state.showGasDetails}
351− toggleGasDetails={this.toggleGasDetails}
352− // Checks
353− userApproval={state.userApproval}
354− canGenerate={ canGenerate }
170+ canGenerate={ canGenerate } />
355− canSign={ canSign }
356− canApprove={ canApprove }
357− canSend={ canSend }
358− // Methods
359− createUnsignedTxn={this.handleCreateUnsignedTxn}
360− setUserApproval={this.handleSetUserApproval}
361− setTxn={this.handleSetTxn}
362− setStx={this.handleSetStx}
363− setNonce={this.handleSetNonce}
364− setChainId={this.handleSetChainId}
365− setGasPrice={this.handleSetGasPrice}
366− setGasLimit={this.handleSetGasLimit}
367− handleSubmit={this.handleSubmit} />
368−
369− {
370− Maybe.Nothing.hasInstance(state.txError)
371− ? ''
372− : <Warning className={'mt-8'}>
373− <H3 style={{marginTop: 0, paddingTop: 0}}>
374− {
375− 'There was an error sending your transaction.'
376− }
377− </H3>
378− { state.txError.value }
379− </Warning>
380− }
381−
382 </Col>
171 </Col>
383 </Row>
172 </Row>
384 )
173 )
src/bridge/views/Shards.js+44−15
@@ -1,7 +1,14 @@
1−import Maybe from 'folktale/maybe'
1+import { Just, Nothing } from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3import { Button } from '../components/Base'
3import { Button } from '../components/Base'
4+import {
5+ InnerLabel,
6+ ValidatedSigil,
7+ PointInput,
8+ ShardInput,
9+ InputCaption,
10+ Input
4−import { InnerLabel, ValidatedSigil, PointInput, ShardInput } from '../components/Base'
11+ } from '../components/Base'
5import { Row, Col, H1, P } from '../components/Base'
12import { Row, Col, H1, P } from '../components/Base'
6import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
13import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
7import * as ob from 'urbit-ob'
14import * as ob from 'urbit-ob'
@@ -33,12 +40,14 @@ class Shards extends React.Component {
33 shard1: '',
40 shard1: '',
34 shard2: '',
41 shard2: '',
35 shard3: '',
42 shard3: '',
43+ passphrase: '',
36 pointName: ''
44 pointName: ''
37 }
45 }
38
46
39 this.pointPlaceholder = placeholder(4)
47 this.pointPlaceholder = placeholder(4)
40 this.ticketPlaceholder = placeholder(16)
48 this.ticketPlaceholder = placeholder(16)
41
49
50+ this.handlePassphraseInput = this.handlePassphraseInput.bind(this)
42 this.handleShardInput = this.handleShardInput.bind(this)
51 this.handleShardInput = this.handleShardInput.bind(this)
43 this.handlePointNameInput = this.handlePointNameInput.bind(this)
52 this.handlePointNameInput = this.handlePointNameInput.bind(this)
44 }
53 }
@@ -53,19 +62,17 @@ class Shards extends React.Component {
53 }
62 }
54 }
63 }
55
64
65+ handlePassphraseInput(passphrase) {
66+ this.setState({ passphrase })
67+ }
68+
56 handlePointNameInput(pointName) {
69 handlePointNameInput(pointName) {
57 if (pointName.length < 15) {
70 if (pointName.length < 15) {
58 this.setState({ pointName })
71 this.setState({ pointName })
59 }
72 }
60 }
73 }
61
74
62− // buttonTriState = (wallet) => {
63− // if (wallet.isNothing()) return 'blue'
64− // if (wallet === false) return 'yellow'
65− // if (Maybe.Nothing.hasInstance(wallet)) return 'green'
66− // }
67−
68− async walletFromShards(shard1, shard2, shard3, pointName) {
75+ async walletFromShards(shard1, shard2, shard3, pointName, passphrase) {
69 const { setWallet, setUrbitWallet } = this.props
76 const { setWallet, setUrbitWallet } = this.props
70
77
71 const s1 = shard1 === '' ? undefined : shard1
78 const s1 = shard1 === '' ? undefined : shard1
@@ -82,18 +89,19 @@ class Shards extends React.Component {
82 if (ticket !== undefined) {
89 if (ticket !== undefined) {
83 const urbitWallet = await kg.generateWallet({
90 const urbitWallet = await kg.generateWallet({
84 ticket: ticket,
91 ticket: ticket,
85− ship: ob.patp2dec(pointName)
92+ ship: ob.patp2dec(pointName),
93+ passphrase: passphrase
86 })
94 })
87 const mnemonic = urbitWallet.ownership.seed
95 const mnemonic = urbitWallet.ownership.seed
88− const wallet = walletFromMnemonic(mnemonic, DEFAULT_HD_PATH)
96+ const wallet = walletFromMnemonic(mnemonic, DEFAULT_HD_PATH, passphrase)
89 setWallet(wallet)
97 setWallet(wallet)
90− setUrbitWallet(Maybe.Just(urbitWallet))
98+ setUrbitWallet(Just(urbitWallet))
91 }
99 }
92 }
100 }
93
101
94 render() {
102 render() {
95 const { popRoute, pushRoute, wallet } = this.props
103 const { popRoute, pushRoute, wallet } = this.props
96− const { shard1, shard2, shard3, pointName } = this.state
104+ const { shard1, shard2, shard3, pointName, passphrase } = this.state
97
105
98 const phPoint = this.pointPlaceholder
106 const phPoint = this.pointPlaceholder
99 const phTick = this.ticketPlaceholder
107 const phTick = this.ticketPlaceholder
@@ -169,13 +177,34 @@ class Shards extends React.Component {
169 <InnerLabel>{ 'Shard 3' }</InnerLabel>
177 <InnerLabel>{ 'Shard 3' }</InnerLabel>
170 </ShardInput>
178 </ShardInput>
171
179
180+ <InputCaption>
181+ { 'If your wallet requires a passphrase, you may enter it below.' }
182+ </InputCaption>
183+
184+ <Input
185+ className='pt-8'
186+ prop-size='md'
187+ prop-format='innerLabel'
188+ name='passphrase'
189+ type='password'
190+ value={ passphrase }
191+ autocomplete='off'
192+ onChange={ this.handlePassphraseInput }>
193+ <InnerLabel>{'Passphrase'}</InnerLabel>
194+ </Input>
195+
172 <Button
196 <Button
173 disabled={ !ready }
197 disabled={ !ready }
174 className={'mt-8'}
198 className={'mt-8'}
175 prop-size={'lg wide'}
199 prop-size={'lg wide'}
176 // prop-color={this.buttonTriState(wallet)}
200 // prop-color={this.buttonTriState(wallet)}
177 onClick={() =>
201 onClick={() =>
178− this.walletFromShards(shard1, shard2, shard3, pointName)
202+ this.walletFromShards(
203+ shard1,
204+ shard2,
205+ shard3,
206+ pointName,
207+ passphrase)
179 }>
208 }>
180 {'Unlock Wallet →'}
209 {'Unlock Wallet →'}
181 </Button>
210 </Button>
@@ -183,7 +212,7 @@ class Shards extends React.Component {
183 <Button
212 <Button
184 className={'mt-4'}
213 className={'mt-4'}
185 prop-size={'xl wide'}
214 prop-size={'xl wide'}
186− disabled={ Maybe.Nothing.hasInstance(wallet) }
215+ disabled={ Nothing.hasInstance(wallet) }
187 onClick={ () => {
216 onClick={ () => {
188 popRoute()
217 popRoute()
189 pushRoute(ROUTE_NAMES.SHIPS)
218 pushRoute(ROUTE_NAMES.SHIPS)
src/bridge/views/Ticket.js+42−17
@@ -1,7 +1,14 @@
1−import Maybe from 'folktale/maybe'
1+import { Just, Nothing } from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
3import { Button } from '../components/Base'
3import { Button } from '../components/Base'
4+import {
5+ InnerLabel,
6+ ValidatedSigil,
7+ PointInput,
8+ TicketInput,
9+ Input,
10+ InputCaption
4−import { InnerLabel, ValidatedSigil, PointInput, TicketInput } from '../components/Base'
11+ } from '../components/Base'
5import { Row, Col, H1, P } from '../components/Base'
12import { Row, Col, H1, P } from '../components/Base'
6import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
13import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
7import * as ob from 'urbit-ob'
14import * as ob from 'urbit-ob'
@@ -26,6 +33,7 @@ class Ticket extends React.Component {
26 this.state = {
33 this.state = {
27 ticket: '',
34 ticket: '',
28 pointName: '',
35 pointName: '',
36+ passphrase: '',
29 isUnlocking: false
37 isUnlocking: false
30 }
38 }
31
39
@@ -33,6 +41,7 @@ class Ticket extends React.Component {
33 this.ticketPlaceholder = placeholder(8)
41 this.ticketPlaceholder = placeholder(8)
34
42
35 this.handleTicketInput = this.handleTicketInput.bind(this)
43 this.handleTicketInput = this.handleTicketInput.bind(this)
44+ this.handlePassphraseInput = this.handlePassphraseInput.bind(this)
36 this.handlePointNameInput = this.handlePointNameInput.bind(this)
45 this.handlePointNameInput = this.handlePointNameInput.bind(this)
37 }
46 }
38
47
@@ -40,19 +49,17 @@ class Ticket extends React.Component {
40 this.setState({ ticket })
49 this.setState({ ticket })
41 }
50 }
42
51
52+ handlePassphraseInput(passphrase) {
53+ this.setState({ passphrase })
54+ }
55+
43 handlePointNameInput(pointName) {
56 handlePointNameInput(pointName) {
44 if (pointName.length < 15) {
57 if (pointName.length < 15) {
45 this.setState({ pointName })
58 this.setState({ pointName })
46 }
59 }
47 }
60 }
48
61
49− // buttonTriState = (wallet) => {
50− // if (wallet.isNothing()) return 'blue'
51− // if (wallet === false) return 'yellow'
52− // if (Maybe.Nothing.hasInstance(wallet)) return 'green'
53− // }
54−
55− async walletFromTicket(ticket, pointName) {
62+ async walletFromTicket(ticket, pointName, passphrase) {
56 const { setWallet, setUrbitWallet } = this.props
63 const { setWallet, setUrbitWallet } = this.props
57
64
58 this.setState({
65 this.setState({
@@ -61,12 +68,13 @@ class Ticket extends React.Component {
61
68
62 const urbitWallet = await kg.generateWallet({
69 const urbitWallet = await kg.generateWallet({
63 ticket: ticket,
70 ticket: ticket,
64− ship: ob.patp2dec(pointName)
71+ ship: ob.patp2dec(pointName),
72+ passphrase: passphrase
65 })
73 })
66 const mnemonic = urbitWallet.ownership.seed
74 const mnemonic = urbitWallet.ownership.seed
67− const wallet = walletFromMnemonic(mnemonic, DEFAULT_HD_PATH)
75+ const wallet = walletFromMnemonic(mnemonic, DEFAULT_HD_PATH, passphrase)
68 setWallet(wallet)
76 setWallet(wallet)
69− setUrbitWallet(Maybe.Just(urbitWallet))
77+ setUrbitWallet(Just(urbitWallet))
70
78
71 this.setState({
79 this.setState({
72 isUnlocking: false
80 isUnlocking: false
@@ -75,7 +83,7 @@ class Ticket extends React.Component {
75
83
76 render() {
84 render() {
77 const { popRoute, pushRoute, wallet } = this.props
85 const { popRoute, pushRoute, wallet } = this.props
78− const { ticket, pointName } = this.state
86+ const { ticket, pointName, passphrase } = this.state
79
87
80 const phPoint = this.pointPlaceholder
88 const phPoint = this.pointPlaceholder
81 const phTick = this.ticketPlaceholder
89 const phTick = this.ticketPlaceholder
@@ -119,12 +127,29 @@ class Ticket extends React.Component {
119 <InnerLabel>{ 'Ticket' }</InnerLabel>
127 <InnerLabel>{ 'Ticket' }</InnerLabel>
120 </TicketInput>
128 </TicketInput>
121
129
130+ <InputCaption>
131+ { 'If your wallet requires a passphrase, you may enter it below.' }
132+ </InputCaption>
133+
134+ <Input
135+ className='pt-8'
136+ prop-size='md'
137+ prop-format='innerLabel'
138+ name='passphrase'
139+ type='password'
140+ value={ passphrase }
141+ autocomplete='off'
142+ onChange={ this.handlePassphraseInput }>
143+ <InnerLabel>{'Passphrase'}</InnerLabel>
144+ </Input>
145+
122 <Button
146 <Button
123 className={'mt-8'}
147 className={'mt-8'}
124 prop-size={'lg wide'}
148 prop-size={'lg wide'}
125− disabled={this.state.isUnlocking || Maybe.Just.hasInstance(wallet)}
149+ disabled={this.state.isUnlocking || Just.hasInstance(wallet)}
126− // prop-color={this.buttonTriState(wallet)}
150+ onClick={
127− onClick={() => this.walletFromTicket(ticket, pointName)}>
151+ () => this.walletFromTicket(ticket, pointName, passphrase)
152+ }>
128
153
129 <span className="relative">
154 <span className="relative">
130 {this.state.isUnlocking &&
155 {this.state.isUnlocking &&
@@ -137,7 +162,7 @@ class Ticket extends React.Component {
137 <Button
162 <Button
138 className={'mt-4'}
163 className={'mt-4'}
139 prop-size={'xl wide'}
164 prop-size={'xl wide'}
140− disabled={ Maybe.Nothing.hasInstance(wallet) }
165+ disabled={ Nothing.hasInstance(wallet) }
141 onClick={ () => {
166 onClick={ () => {
142 popRoute()
167 popRoute()
143 pushRoute(ROUTE_NAMES.SHIPS)
168 pushRoute(ROUTE_NAMES.SHIPS)
src/bridge/views/Transfer.js+12−222
@@ -4,22 +4,15 @@ import * as azimuth from 'azimuth-js'
4import * as ob from 'urbit-ob'
4import * as ob from 'urbit-ob'
5
5
6import { Row, Col, H1, InnerLabel, ShowBlockie, P, Anchor } from '../components/Base'
6import { Row, Col, H1, InnerLabel, ShowBlockie, P, Anchor } from '../components/Base'
7−import { AddressInput, Warning, H3 } from '../components/Base'
7+import { AddressInput } from '../components/Base'
8import StatelessTransaction from '../components/StatelessTransaction'
8import StatelessTransaction from '../components/StatelessTransaction'
9
9
10import { BRIDGE_ERROR } from '../lib/error'
10import { BRIDGE_ERROR } from '../lib/error'
11import { NETWORK_NAMES } from '../lib/network'
11import { NETWORK_NAMES } from '../lib/network'
12−import { ROUTE_NAMES } from '../lib/router'
13
12
14import {
13import {
15− sendSignedTransaction,
16− fromWei,
17− } from '../lib/txn'
18−
19− import {
20− isValidAddress,
14+ isValidAddress
21− addressFromSecp256k1Public
22− } from '../lib/wallet'
15+} from '../lib/wallet'
23
16
24class Transfer extends React.Component {
17class Transfer extends React.Component {
25 constructor(props) {
18 constructor(props) {
@@ -33,191 +26,26 @@ class Transfer extends React.Component {
33 })
26 })
34
27
35 this.state = {
28 this.state = {
36− txError: Maybe.Nothing(),
37 receivingAddress: '',
29 receivingAddress: '',
38 issuingPoint: issuingPoint,
30 issuingPoint: issuingPoint,
39− userApproval: false,
40− nonce: '',
41− gasPrice: '5',
42− showGasDetails: false,
43− chainId: '',
44− gasLimit: '600000',
45− txn: Maybe.Nothing(),
46− stx: Maybe.Nothing(),
47 }
31 }
48− this.handleAddressInput = this.handleAddressInput.bind(this)
49− // Transaction
50− this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
51− this.handleSubmit = this.handleSubmit.bind(this)
52− this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
53− this.handleSetTxn = this.handleSetTxn.bind(this)
54− this.handleSetStx = this.handleSetStx.bind(this)
55− this.handleSetNonce = this.handleSetNonce.bind(this)
56− this.handleSetChainId = this.handleSetChainId.bind(this)
57− this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
58− this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
59− this.toggleGasDetails = this.toggleGasDetails.bind(this)
60− }
61−
62− toggleGasDetails() {
63− this.setState({
64− showGasDetails: !this.state.showGasDetails
65− })
66− }
67−
68− componentDidMount() {
69− const { props } = this
70−
71− const addr = props.wallet.matchWith({
72− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
73− Nothing: () => {
74− throw BRIDGE_ERROR.MISSING_WALLET
75− }
76− })
77−
78− props.web3.matchWith({
79− Nothing: () => {},
80− Just: (w3) => {
81− const validWeb3 = w3.value
82−
83− const getTxMetadata = [
84− validWeb3.eth.getTransactionCount(addr),
85− validWeb3.eth.net.getId(),
86− validWeb3.eth.getGasPrice()
87− ];
88−
89− Promise.all(getTxMetadata).then(r => {
90− const txMetadata = {
91− nonce: r[0],
92− chainId: r[1],
93− gasPrice: fromWei(r[2], 'gwei'),
94− };
95−
96− this.setState({...txMetadata})
97−
98− })
99− }
100− });
101
32
33+ this.handleAddressInput = this.handleAddressInput.bind(this)
34+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
35+ this.statelessRef = React.createRef();
102 }
36 }
103
37
104−
105−
106 handleAddressInput(receivingAddress) {
38 handleAddressInput(receivingAddress) {
107 this.setState({ receivingAddress })
39 this.setState({ receivingAddress })
108− this.handleClearTxn()
40+ this.statelessRef.current.clearTxn()
109 }
41 }
110
42
111−
112−
113 handleConfirmAvailability() {
43 handleConfirmAvailability() {
114 this.confirmPointAvailability().then(r => {
44 this.confirmPointAvailability().then(r => {
115 this.setState({ isAvailable: r })
45 this.setState({ isAvailable: r })
116 })
46 })
117 }
47 }
118
48
119−
120−
121− handleCreateUnsignedTxn() {
122− const txn = this.createUnsignedTxn()
123− this.setState({ txn })
124− }
125−
126−
127− handleSetUserApproval(){
128− const {state} = this
129− this.setState({ userApproval: !state.userApproval })
130− }
131−
132−
133−
134− handleSetTxn(txn){
135− this.setState({ txn })
136− }
137−
138−
139−
140− handleSetStx(stx){
141− this.setState({
142− stx,
143− userApproval: false,
144− })
145− }
146−
147−
148−
149− handleSetNonce(nonce){
150− this.setState({ nonce })
151− this.handleClearStx()
152− }
153−
154−
155−
156− handleSetChainId(chainId){
157− this.setState({ chainId })
158− this.handleClearStx()
159− }
160−
161−
162−
163− handleSetGasPrice(gasPrice){
164− this.setState({ gasPrice })
165− this.handleClearStx()
166− }
167−
168−
169−
170− handleSetGasLimit(gasLimit){
171− this.setState({ gasLimit })
172− this.handleClearStx()
173− }
174−
175−
176−
177− handleClearStx() {
178− this.setState({
179− userApproval: false,
180− stx: Maybe.Nothing(),
181− })
182− }
183−
184−
185−
186− handleClearTxn() {
187− this.setState({
188− userApproval: false,
189− txn: Maybe.Nothing(),
190− stx: Maybe.Nothing(),
191− })
192− }
193−
194−
195−
196− handleClearTransaction() {
197− this.setState({
198− userApproval: false,
199− txn: Maybe.Nothing(),
200− stx: Maybe.Nothing(),
201− })
202− }
203−
204−
205−
206− handleSubmit(){
207− const { props, state } = this
208− sendSignedTransaction(props.web3.value, state.stx)
209− .then(sent => {
210− props.setTxnHashCursor(sent)
211− props.popRoute()
212− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
213− })
214− .catch(err => {
215− this.setState({ txError: err.map(val => val.merge()) })
216− })
217− }
218−
219−
220−
221 createUnsignedTxn() {
49 createUnsignedTxn() {
222 const { state, props } = this
50 const { state, props } = this
223
51
@@ -244,19 +72,12 @@ class Transfer extends React.Component {
244 return Maybe.Just(txn)
72 return Maybe.Just(txn)
245 }
73 }
246
74
247−
248−
249 render() {
75 render() {
250 const { props, state } = this
76 const { props, state } = this
251
77
252 const validAddress = isValidAddress(state.receivingAddress)
78 const validAddress = isValidAddress(state.receivingAddress)
253−
254 const canGenerate = validAddress === true
79 const canGenerate = validAddress === true
255
80
256− const canSign = Maybe.Just.hasInstance(state.txn)
257− const canApprove = Maybe.Just.hasInstance(state.stx)
258− const canSend = Maybe.Just.hasInstance(state.stx) && state.userApproval === true
259−
260 const esvisible =
81 const esvisible =
261 props.networkType === NETWORK_NAMES.ROPSTEN ||
82 props.networkType === NETWORK_NAMES.ROPSTEN ||
262 props.networkType === NETWORK_NAMES.MAINNET
83 props.networkType === NETWORK_NAMES.MAINNET
@@ -310,46 +131,15 @@ class Transfer extends React.Component {
310 walletType={props.walletType}
131 walletType={props.walletType}
311 walletHdPath={props.walletHdPath}
132 walletHdPath={props.walletHdPath}
312 networkType={props.networkType}
133 networkType={props.networkType}
313− // Tx
134+ setTxnHashCursor={props.setTxnHashCursor}
314− txn={state.txn}
135+ popRoute={props.popRoute}
315− stx={state.stx}
136+ pushRoute={props.pushRoute}
316− // Tx details
317− nonce={state.nonce}
318− gasPrice={state.gasPrice}
319− chainId={state.chainId}
320− gasLimit={state.gasLimit}
321− showGasDetails={state.showGasDetails}
322− toggleGasDetails={this.toggleGasDetails}
323 // Checks
137 // Checks
324 userApproval={state.userApproval}
138 userApproval={state.userApproval}
325 canGenerate={ canGenerate }
139 canGenerate={ canGenerate }
326− canSign={ canSign }
327− canApprove={ canApprove }
328− canSend={ canSend }
329 // Methods
140 // Methods
330− createUnsignedTxn={this.handleCreateUnsignedTxn}
141+ createUnsignedTxn={this.createUnsignedTxn}
331− setUserApproval={this.handleSetUserApproval}
142+ ref={this.statelessRef} />
332− setTxn={this.handleSetTxn}
333− setStx={this.handleSetStx}
334− setNonce={this.handleSetNonce}
335− setChainId={this.handleSetChainId}
336− setGasPrice={this.handleSetGasPrice}
337− setGasLimit={this.handleSetGasLimit}
338− handleSubmit={this.handleSubmit} />
339−
340− {
341− Maybe.Nothing.hasInstance(state.txError)
342− ? ''
343− : <Warning className={'mt-8'}>
344− <H3 style={{marginTop: 0, paddingTop: 0}}>
345− {
346− 'There was an error sending your transaction.'
347− }
348− </H3>
349− { state.txError.value }
350− </Warning>
351− }
352−
353 </Col>
143 </Col>
354 </Row>
144 </Row>
355 )
145 )
src/common/components/AppNavigation.js+5−5
@@ -23,17 +23,17 @@ class AppNavigation extends React.Component {
23 let style = {
23 let style = {
24 position: "fixed",
24 position: "fixed",
25 bottom: "10px",
25 bottom: "10px",
26− right: "10px"
26+ right: "10px",
27+ "text-decoration": "underline",
28+ cursor: "pointer"
27 }
29 }
28
30
29 return (
31 return (
30− <a
32+ <span
31− href="javascript:void(0)"
32− type="button"
33 style={style}
33 style={style}
34 onClick={this.navigate}>
34 onClick={this.navigate}>
35 {buttonText}
35 {buttonText}
36− </a>
36+ </span>
37 )
37 )
38 }
38 }
39}
39}