on master help sign in
Merge pull request #26 from urbit/jt-issue-12
23d8ffbWill Kim
package-lock.json+29−8
@@ -952,17 +952,28 @@
952 "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw=="
952 "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw=="
953 },
953 },
954 "@ledgerhq/hw-app-eth": {
954 "@ledgerhq/hw-app-eth": {
955− "version": "4.32.0",
955+ "version": "4.35.0",
956− "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-4.32.0.tgz",
956+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-4.35.0.tgz",
957− "integrity": "sha512-d22WinjcsqJNoZSI+6UpTWZ7hl+UhL2dFeVeliCwtBWSj40z6F25MpoviGxPsv0WC7IUjayw+a9jIRcOJ5kkIw==",
957+ "integrity": "sha512-MSDr8+CaoXhtm64ELuI/8wpcfmrMUjzGJgASY6bnjc82vAW+6sHNZlTU0zWRTZxqQUuZ8WpuJP159cf92MWq3g==",
958 "requires": {
958 "requires": {
959− "@ledgerhq/hw-transport": "^4.32.0"
959+ "@ledgerhq/hw-transport": "^4.35.0"
960+ },
961+ "dependencies": {
962+ "@ledgerhq/hw-transport": {
963+ "version": "4.35.0",
964+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.35.0.tgz",
965+ "integrity": "sha512-o8ekdoCkHMvOByIKDmAMNDjm8Q5cu+sbqmebPtGrHAPbgIZBUbNA5UupY/Om+xypdxXYnuBw+MF8FyIVOjnIsg==",
966+ "requires": {
967+ "events": "^3.0.0"
968+ }
969+ }
960 }
970 }
961 },
971 },
962 "@ledgerhq/hw-transport": {
972 "@ledgerhq/hw-transport": {
963 "version": "4.32.0",
973 "version": "4.32.0",
964 "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.32.0.tgz",
974 "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.32.0.tgz",
965 "integrity": "sha512-Wgsk9UHC4RShqYoDeIEeKgHZOvNCtB0WWIG0xqlVPzS+IcKDkIxtXQw7hTA7GQSuDuGeauVtlbTQ5yat6+2/BA==",
975 "integrity": "sha512-Wgsk9UHC4RShqYoDeIEeKgHZOvNCtB0WWIG0xqlVPzS+IcKDkIxtXQw7hTA7GQSuDuGeauVtlbTQ5yat6+2/BA==",
976+ "dev": true,
966 "requires": {
977 "requires": {
967 "events": "^3.0.0"
978 "events": "^3.0.0"
968 }
979 }
@@ -980,12 +991,22 @@
980 }
991 }
981 },
992 },
982 "@ledgerhq/hw-transport-u2f": {
993 "@ledgerhq/hw-transport-u2f": {
983− "version": "4.32.0",
994+ "version": "4.35.0",
984− "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.32.0.tgz",
995+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.35.0.tgz",
985− "integrity": "sha512-xzY+Q7ObVVv/V/wxvp1eEbDhFf+LFnavQGvqw8EZkCEmkX4iKh2w7AH+B8k1XWS+Mpa3CiG3TVQQE85mf+6jQg==",
996+ "integrity": "sha512-cVPH0SNRrIKVZz9zHvulHGPSdDQiV1hCK634KZa2M2+fMVhQxUi4YRBQz7QpI88CwRyEy+sBfTfSahPWjQUN2Q==",
986 "requires": {
997 "requires": {
987− "@ledgerhq/hw-transport": "^4.32.0",
998+ "@ledgerhq/hw-transport": "^4.35.0",
988 "u2f-api": "0.2.7"
999 "u2f-api": "0.2.7"
1000+ },
1001+ "dependencies": {
1002+ "@ledgerhq/hw-transport": {
1003+ "version": "4.35.0",
1004+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.35.0.tgz",
1005+ "integrity": "sha512-o8ekdoCkHMvOByIKDmAMNDjm8Q5cu+sbqmebPtGrHAPbgIZBUbNA5UupY/Om+xypdxXYnuBw+MF8FyIVOjnIsg==",
1006+ "requires": {
1007+ "events": "^3.0.0"
1008+ }
1009+ }
989 }
1010 }
990 },
1011 },
991 "@mrmlnc/readdir-enhanced": {
1012 "@mrmlnc/readdir-enhanced": {
package.json+2−2
@@ -14,9 +14,9 @@
14 "node": "11.0.0"
14 "node": "11.0.0"
15 },
15 },
16 "dependencies": {
16 "dependencies": {
17− "@ledgerhq/hw-app-eth": "^4.24.0",
18− "@ledgerhq/hw-transport-u2f": "^4.28.0",
19 "azimuth-js": "^0.10.0",
17 "azimuth-js": "^0.10.0",
18+ "@ledgerhq/hw-app-eth": "^4.35.0",
19+ "@ledgerhq/hw-transport-u2f": "^4.35.0",
20 "azimuth-solidity": "1.0.2",
20 "azimuth-solidity": "1.0.2",
21 "babel-polyfill": "^6.26.0",
21 "babel-polyfill": "^6.26.0",
22 "bip32": "^1.0.2",
22 "bip32": "^1.0.2",
src/components/StatelessTransaction.js+1
@@ -114,6 +114,7 @@ const StatelessTransaction = props => {
114 wallet: props.wallet,
114 wallet: props.wallet,
115 walletType: props.walletType,
115 walletType: props.walletType,
116 walletHdPath: props.walletHdPath,
116 walletHdPath: props.walletHdPath,
117+ networkType: props.networkType,
117 txn: props.txn,
118 txn: props.txn,
118 setStx: props.setStx,
119 setStx: props.setStx,
119 nonce: props.nonce,
120 nonce: props.nonce,
src/lib/txn.js+50−74
@@ -5,6 +5,7 @@ import Tx from 'ethereumjs-tx'
5import Web3 from 'web3'
5import Web3 from 'web3'
6
6
7import { BRIDGE_ERROR } from '../lib/error'
7import { BRIDGE_ERROR } from '../lib/error'
8+import { NETWORK_NAMES } from '../lib/network'
8import { ledgerSignTransaction } from '../lib/ledger'
9import { ledgerSignTransaction } from '../lib/ledger'
9import { trezorSignTransaction } from '../lib/trezor'
10import { trezorSignTransaction } from '../lib/trezor'
10import {
11import {
@@ -12,7 +13,6 @@ import {
12 addHexPrefix
13 addHexPrefix
13 } from '../lib/wallet'
14 } from '../lib/wallet'
14
15
15−
16const TXN_PURPOSE = {
16const TXN_PURPOSE = {
17 SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
17 SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
18 SET_TRANSFER_PROXY: Symbol('SET_TRANSFER_PROXY'),
18 SET_TRANSFER_PROXY: Symbol('SET_TRANSFER_PROXY'),
@@ -43,41 +43,13 @@ const renderTxnPurpose = (purpose) =>
43 ? 'cancel this transfer'
43 ? 'cancel this transfer'
44 : 'perform this transaction'
44 : 'perform this transaction'
45
45
46−
47−
48−
49−
50−// const createUnsignedTxn = config => {
51−// const {
52−// contracts,
53−// galaxyName,
54−// add,
55−// setTxn
56−// } = config
57−//
58−// const ctrcs = contracts.matchWith({
59−// Just: (cs) => cs.value,
60−// Nothing: () => {
61−// throw BRIDGE_ERROR.MISSING_CONTRACTS
62−// }
63−// })
64−//
65−// const galaxy = parseInt(ob.patp2dec(galaxyName), 10)
66−//
67−// const txn = isValidGalaxy(galaxyName) && isValidAddress(galaxyOwner)
68−// ? Maybe.Just(azimuth.ecliptic.createGalaxy(ctrcs, galaxy, galaxyOwner))
69−// : Maybe.Nothing()
70−//
71−// setTxn(txn)
72−// }
73−
74−
75const signTransaction = async config => {
46const signTransaction = async config => {
76
47
77 let {
48 let {
78 wallet,
49 wallet,
79 walletType,
50 walletType,
80 walletHdPath,
51 walletHdPath,
52+ networkType,
81 txn,
53 txn,
82 setStx,
54 setStx,
83 nonce,
55 nonce,
@@ -91,18 +63,62 @@ const signTransaction = async config => {
91 gasPrice = toHex(toWei(gasPrice, 'gwei'))
63 gasPrice = toHex(toWei(gasPrice, 'gwei'))
92 gasLimit = toHex(gasLimit)
64 gasLimit = toHex(gasLimit)
93
65
66+ const txParams = { nonce, chainId, gasPrice, gasLimit }
67+
68+ // NB (jtobin)
69+ //
70+ // Ledger does not seem to handle EIP-155 automatically. When using a Ledger,
71+ // if the block number is at least FORK_BLKNUM = 2675000, one needs to
72+ // pre-set the ECDSA signature parameters with r = 0, s = 0, and v = chainId
73+ // prior to signing.
74+ //
75+ // The easiest way to handle this is to just branch on the network, since
76+ // mainnet and Ropsten have obviously passed FORK_BLKNUM. This is somewhat
77+ // awkward when dealing with offline transactions, since we might want to
78+ // test them on a local network as well.
79+ //
80+ // The best thing to do is probably to add an 'advanced' tab to offline
81+ // transaction generation where one can disable the defaulted-on EIP-155
82+ // settings in this case. This is pretty low-priority, but is a
83+ // comprehensive solution.
84+ //
85+ // See:
86+ //
87+ // See https://github.com/LedgerHQ/ledgerjs/issues/43#issuecomment-366984725
88+ //
89+ // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
90+
91+ const eip155Params = {
92+ r: '0x00',
93+ s: '0x00',
94+ v: chainId
95+ }
96+
97+ const defaultEip155Networks = [
98+ NETWORK_NAMES.MAINNET,
99+ NETWORK_NAMES.ROPSTEN,
100+ NETWORK_NAMES.OFFLINE
101+ ]
102+
103+ const needEip155Params =
104+ walletType === WALLET_NAMES.LEDGER &&
105+ defaultEip155Networks.includes(networkType)
106+
107+ const signingParams =
108+ needEip155Params
109+ ? Object.assign(txParams, eip155Params)
110+ : txParams
111+
94 const wal = wallet.matchWith({
112 const wal = wallet.matchWith({
95 Just: (w) => w.value,
113 Just: (w) => w.value,
96− Nothing: () => {
97− throw BRIDGE_ERROR.MISSING_WALLET
114+ Nothing: () => { throw BRIDGE_ERROR.MISSING_WALLET }
98− }
99 })
115 })
100
116
101 const sec = wal.privateKey
117 const sec = wal.privateKey
102
118
103 const utx = txn.matchWith({
119 const utx = txn.matchWith({
104 Just: (tx) =>
120 Just: (tx) =>
105− Object.assign(tx.value, { nonce, chainId, gasPrice, gasLimit }),
121+ Object.assign(tx.value, signingParams),
106 Nothing: () => {
122 Nothing: () => {
107 throw BRIDGE_ERROR.MISSING_TXN
123 throw BRIDGE_ERROR.MISSING_TXN
108 }
124 }
@@ -121,8 +137,6 @@ const signTransaction = async config => {
121 setStx(Maybe.Just(stx))
137 setStx(Maybe.Just(stx))
122}
138}
123
139
124−
125−
126const sendSignedTransaction = (web3, stx) => {
140const sendSignedTransaction = (web3, stx) => {
127 const txn = stx.matchWith({
141 const txn = stx.matchWith({
128 Just: (tx) => tx.value,
142 Just: (tx) => tx.value,
@@ -170,28 +184,6 @@ const toHex = dummy.utils.toHex
170const toWei = dummy.utils.toWei
184const toWei = dummy.utils.toWei
171const fromWei = dummy.utils.fromWei
185const fromWei = dummy.utils.fromWei
172
186
173−
174−// const confirmShipAvailability = (point, contracts) => {
175−//
176−// if (canDecodePatp(point) === true) {
177−//
178−// const pointDec = ob.patp2dec(point)
179−//
180−// contracts.matchWith({
181−// Nothing: () => {
182−// throw BRIDGE_ERROR.MISSING_CONTRACTS
183−// },
184−// Just: async (contracts) => {
185−// const owner = await azimuth.azimuth.getOwner(contracts.value, point)
186−// if (eqAddr(owner, ETH_ZERO_ADDR)) return true
187−// return false
188−// }
189−// })
190−// }
191−// return false
192−// }
193−
194−
195const canDecodePatp = p => {
187const canDecodePatp = p => {
196 try {
188 try {
197 ob.patp2dec(p)
189 ob.patp2dec(p)
@@ -201,21 +193,6 @@ const canDecodePatp = p => {
201 }
193 }
202}
194}
203
195
204−
205−
206−
207−
208−// const bridgeAPI = {
209−// shipAvailability: {
210−// planet: planet => {},
211−// star: star => {},
212−// galaxy: galaxy => {},
213−// },
214−// // dummyWeb3: {
215−// //
216−// // }
217−// }
218−
219export {
196export {
220 signTransaction,
197 signTransaction,
221 sendSignedTransaction,
198 sendSignedTransaction,
@@ -228,5 +205,4 @@ export {
228 toWei,
205 toWei,
229 fromWei,
206 fromWei,
230 canDecodePatp,
207 canDecodePatp,
231− // confirmShipAvailability,
232}
208}
src/views/AcceptTransfer.js+12−2
@@ -7,6 +7,7 @@ import * as ob from 'urbit-ob'
7
7
8import StatelessTransaction from '../components/StatelessTransaction'
8import StatelessTransaction from '../components/StatelessTransaction'
9import { BRIDGE_ERROR } from '../lib/error'
9import { BRIDGE_ERROR } from '../lib/error'
10+import { NETWORK_NAMES } from '../lib/network'
10import { ROUTE_NAMES } from '../lib/router'
11import { ROUTE_NAMES } from '../lib/router'
11
12
12import {
13import {
@@ -263,6 +264,14 @@ class AcceptTransfer extends React.Component {
263 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
264 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
264 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
265 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
265
266
267+ const esvisible =
268+ props.networkType === NETWORK_NAMES.ROPSTEN ||
269+ props.networkType === NETWORK_NAMES.MAINNET
270+
271+ const esdomain =
272+ props.networkType === NETWORK_NAMES.ROPSTEN
273+ ? "ropsten.etherscan.io"
274+ : "etherscan.io"
266
275
267 return (
276 return (
268 <Row>
277 <Row>
@@ -290,9 +299,9 @@ class AcceptTransfer extends React.Component {
290 <Anchor
299 <Anchor
291 className={'mt-1 sm'}
300 className={'mt-1 sm'}
292 prop-size='sm'
301 prop-size='sm'
293− prop-disabled={!isValidAddress(state.receivingAddress)}
302+ prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
294 target={'_blank'}
303 target={'_blank'}
295− href={`https://etherscan.io/address/${state.receivingAddress}`}>
304+ href={`https://${esdomain}/address/${state.receivingAddress}`}>
296 {'View on Etherscan ↗'}
305 {'View on Etherscan ↗'}
297 </Anchor>
306 </Anchor>
298
307
@@ -303,6 +312,7 @@ class AcceptTransfer extends React.Component {
303 wallet={props.wallet}
312 wallet={props.wallet}
304 walletType={props.walletType}
313 walletType={props.walletType}
305 walletHdPath={props.walletHdPath}
314 walletHdPath={props.walletHdPath}
315+ networkType={props.networkType}
306 // Tx
316 // Tx
307 txn={state.txn}
317 txn={state.txn}
308 stx={state.stx}
318 stx={state.stx}
src/views/CancelTransfer.js+1
@@ -234,6 +234,7 @@ class CancelTransfer extends React.Component {
234 wallet={props.wallet}
234 wallet={props.wallet}
235 walletType={props.walletType}
235 walletType={props.walletType}
236 walletHdPath={props.walletHdPath}
236 walletHdPath={props.walletHdPath}
237+ networkType={props.networkType}
237 // Tx
238 // Tx
238 txn={state.txn}
239 txn={state.txn}
239 stx={state.stx}
240 stx={state.stx}
src/views/CreateGalaxy.js+13−2
@@ -10,6 +10,8 @@ import StatelessTransaction from '../components/StatelessTransaction'
10
10
11import { BRIDGE_ERROR } from '../lib/error'
11import { BRIDGE_ERROR } from '../lib/error'
12
12
13+import { NETWORK_NAMES } from '../lib/network'
14+
13import {
15import {
14 sendSignedTransaction,
16 sendSignedTransaction,
15 getTxnInfo,
17 getTxnInfo,
@@ -179,6 +181,14 @@ class CreateGalaxy extends React.Component {
179 const validAddress = isValidAddress(state.galaxyOwner)
181 const validAddress = isValidAddress(state.galaxyOwner)
180 const validGalaxy = isValidGalaxy(state.galaxyName)
182 const validGalaxy = isValidGalaxy(state.galaxyName)
181
183
184+ const esvisible =
185+ props.networkType === NETWORK_NAMES.ROPSTEN ||
186+ props.networkType === NETWORK_NAMES.MAINNET
187+
188+ const esdomain =
189+ props.networkType === NETWORK_NAMES.ROPSTEN
190+ ? "ropsten.etherscan.io"
191+ : "etherscan.io"
182
192
183 return (
193 return (
184 <Row>
194 <Row>
@@ -225,9 +235,9 @@ class CreateGalaxy extends React.Component {
225 <Anchor
235 <Anchor
226 className={'mt-1'}
236 className={'mt-1'}
227 prop-size={'sm'}
237 prop-size={'sm'}
228− prop-disabled={!validAddress}
238+ prop-disabled={!validAddress || !esvisible}
229 target={'_blank'}
239 target={'_blank'}
230− href={`https://etherscan.io/address/${state.galaxyOwner}`}>
240+ href={`https://${esdomain}/address/${state.galaxyOwner}`}>
231 {'View on Etherscan ↗'}
241 {'View on Etherscan ↗'}
232 </Anchor>
242 </Anchor>
233
243
@@ -249,6 +259,7 @@ class CreateGalaxy extends React.Component {
249 wallet={props.wallet}
259 wallet={props.wallet}
250 walletType={props.walletType}
260 walletType={props.walletType}
251 walletHdPath={props.walletHdPath}
261 walletHdPath={props.walletHdPath}
262+ networkType={props.networkType}
252 // Tx
263 // Tx
253 txApproval={state.txApproval}
264 txApproval={state.txApproval}
254 txn={state.txn}
265 txn={state.txn}
src/views/IssueChild.js+14−5
@@ -10,6 +10,7 @@ import { PointInput, AddressInput, InnerLabel } from '../components/Base'
10import StatelessTransaction from '../components/StatelessTransaction'
10import StatelessTransaction from '../components/StatelessTransaction'
11
11
12import { ROUTE_NAMES } from '../lib/router'
12import { ROUTE_NAMES } from '../lib/router'
13+import { NETWORK_NAMES } from '../lib/network'
13import { BRIDGE_ERROR } from '../lib/error'
14import { BRIDGE_ERROR } from '../lib/error'
14import { getSpawnCandidate } from '../lib/child'
15import { getSpawnCandidate } from '../lib/child'
15import {
16import {
@@ -337,8 +338,6 @@ class IssueChild extends React.Component {
337 render() {
338 render() {
338 const { props, state } = this
339 const { props, state } = this
339
340
340−
341−
342 const validAddress = isValidAddress(state.receivingAddress)
341 const validAddress = isValidAddress(state.receivingAddress)
343 const validPoint = canDecodePatp(state.desiredPoint)
342 const validPoint = canDecodePatp(state.desiredPoint)
344
343
@@ -354,11 +353,19 @@ class IssueChild extends React.Component {
354 }
353 }
355 })
354 })
356
355
357−
358 const canSign = !Maybe.Nothing.hasInstance(state.txn)
356 const canSign = !Maybe.Nothing.hasInstance(state.txn)
359 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
357 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
360 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
358 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
361
359
360+ const esvisible =
361+ props.networkType === NETWORK_NAMES.ROPSTEN ||
362+ props.networkType === NETWORK_NAMES.MAINNET
363+
364+ const esdomain =
365+ props.networkType === NETWORK_NAMES.ROPSTEN
366+ ? "ropsten.etherscan.io"
367+ : "etherscan.io"
368+
362 return (
369 return (
363 <Row>
370 <Row>
364 <Col>
371 <Col>
@@ -433,9 +440,9 @@ class IssueChild extends React.Component {
433 <Anchor
440 <Anchor
434 className={'mt-1'}
441 className={'mt-1'}
435 prop-size={'sm'}
442 prop-size={'sm'}
436− prop-disabled={!isValidAddress(state.receivingAddress)}
443+ prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
437 target={'_blank'}
444 target={'_blank'}
438− href={`https://etherscan.io/address/${state.receivingAddress}`}>
445+ href={`https://${esdomain}/address/${state.receivingAddress}`}>
439 {'View on Etherscan ↗'}
446 {'View on Etherscan ↗'}
440 </Anchor>
447 </Anchor>
441
448
@@ -461,6 +468,7 @@ class IssueChild extends React.Component {
461 wallet={props.wallet}
468 wallet={props.wallet}
462 walletType={props.walletType}
469 walletType={props.walletType}
463 walletHdPath={props.walletHdPath}
470 walletHdPath={props.walletHdPath}
471+ networkType={props.networkType}
464 // Tx
472 // Tx
465 txn={state.txn}
473 txn={state.txn}
466 stx={state.stx}
474 stx={state.stx}
@@ -498,6 +506,7 @@ class IssueChild extends React.Component {
498 { state.txError.value }
506 { state.txError.value }
499 </Warning>
507 </Warning>
500 }
508 }
509+
501 </Col>
510 </Col>
502 </Row>
511 </Row>
503 )
512 )
src/views/SentTransaction.js+42−12
@@ -4,7 +4,36 @@ import { Button } from '../components/Base'
4
4
5import { BRIDGE_ERROR, renderTxnError } from '../lib/error'
5import { BRIDGE_ERROR, renderTxnError } from '../lib/error'
6
6
7+import { NETWORK_NAMES } from '../lib/network'
8+
7−const Success = (props) =>
9+const Success = (props) => {
10+
11+ const esvisible =
12+ props.networkType === NETWORK_NAMES.ROPSTEN ||
13+ props.networkType === NETWORK_NAMES.MAINNET
14+
15+ const esdomain =
16+ props.networkType === NETWORK_NAMES.ROPSTEN
17+ ? "ropsten.etherscan.io"
18+ : "etherscan.io"
19+
20+ const esmessage =
21+ esvisible === true
22+ ? "If you’d like to keep track of it, click the Etherscan link below."
23+ : ''
24+
25+ const esanchor =
26+ esvisible === false
27+ ? <div />
28+ : <Anchor
29+ className={'mb-4 mt-1'}
30+ prop-size={'sm'}
31+ target={'_blank'}
32+ href={`https://${esdomain}/tx/${props.hash}`}>
33+ {'View on Etherscan ↗'}
34+ </Anchor>
35+
36+ return (
8 <Row>
37 <Row>
9 <Col>
38 <Col>
10 <H1>{ 'Your Transaction was Sent' }</H1>
39 <H1>{ 'Your Transaction was Sent' }</H1>
@@ -12,8 +41,7 @@ const Success = (props) =>
12 <P>
41 <P>
13 {
42 {
14 `We sent your transaction to the chain. It can take some time to
43 `We sent your transaction to the chain. It can take some time to
15− execute, especially if the network is busy. If you’d like to keep
44+ execute, especially if the network is busy. ${esmessage}`
16− track of it, click the Etherscan link below.`
17 }
45 }
18 </P>
46 </P>
19
47
@@ -21,15 +49,13 @@ const Success = (props) =>
21 <P>
49 <P>
22 { props.hash }
50 { props.hash }
23 </P>
51 </P>
24− <Anchor
52+
25− className={'mb-4 mt-1'}
53+ { esanchor }
26− prop-size={'sm'}
54+
27− target={'_blank'}
28− href={`https://etherscan.io/tx/${props.hash}`}>
29− {'View on Etherscan ↗'}
30− </Anchor>
31 </Col>
55 </Col>
32 </Row>
56 </Row>
57+ )
58+}
33
59
34const Failure = (props) =>
60const Failure = (props) =>
35
61
@@ -50,7 +76,7 @@ const Failure = (props) =>
50 </Row>
76 </Row>
51
77
52const SentTransaction = (props) => {
78const SentTransaction = (props) => {
53− const { web3, txnCursor, popRoute } = props
79+ const { web3, txnCursor, networkType, popRoute } = props
54 const { setPointCursor, pointCursor } = props
80 const { setPointCursor, pointCursor } = props
55
81
56 const w3 = web3.matchWith({
82 const w3 = web3.matchWith({
@@ -65,7 +91,11 @@ const SentTransaction = (props) => {
65
91
66 const body = result.matchWith({
92 const body = result.matchWith({
67 Error: message => <Failure web3={ w3 } message={ message.value } />,
93 Error: message => <Failure web3={ w3 } message={ message.value } />,
94+ Ok: txn =>
95+ <Success
68− Ok: txn => <Success hash={ txn.value.transactionHash } />
96+ hash={ txn.value.transactionHash }
97+ networkType={ networkType }
98+ />
69 })
99 })
70
100
71 const ok =
101 const ok =
src/views/SetKeys.js+2
@@ -329,6 +329,8 @@ class SetKeys extends React.Component {
329 contracts={props.contracts}
329 contracts={props.contracts}
330 wallet={props.wallet}
330 wallet={props.wallet}
331 walletType={props.walletType}
331 walletType={props.walletType}
332+ walletHdPath={props.walletHdPath}
333+ networkType={props.networkType}
332 // Tx
334 // Tx
333 txn={state.txn}
335 txn={state.txn}
334 stx={state.stx}
336 stx={state.stx}
src/views/SetProxy.js+14−2
@@ -8,6 +8,8 @@ import * as ob from 'urbit-ob'
8import { PROXY_TYPE, renderProxyType } from '../lib/proxy'
8import { PROXY_TYPE, renderProxyType } from '../lib/proxy'
9import { BRIDGE_ERROR } from '../lib/error'
9import { BRIDGE_ERROR } from '../lib/error'
10
10
11+import { NETWORK_NAMES } from '../lib/network'
12+
11import StatelessTransaction from '../components/StatelessTransaction'
13import StatelessTransaction from '../components/StatelessTransaction'
12
14
13import { ROUTE_NAMES } from '../lib/router'
15import { ROUTE_NAMES } from '../lib/router'
@@ -274,6 +276,14 @@ class SetProxy extends React.Component {
274 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
276 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
275 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
277 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
276
278
279+ const esvisible =
280+ props.networkType === NETWORK_NAMES.ROPSTEN ||
281+ props.networkType === NETWORK_NAMES.MAINNET
282+
283+ const esdomain =
284+ props.networkType === NETWORK_NAMES.ROPSTEN
285+ ? "ropsten.etherscan.io"
286+ : "etherscan.io"
277
287
278 const ucFirst = w => w.charAt(0).toUpperCase() + w.slice(1);
288 const ucFirst = w => w.charAt(0).toUpperCase() + w.slice(1);
279
289
@@ -293,6 +303,7 @@ class SetProxy extends React.Component {
293 'Generator to generate a keypair.'
303 'Generator to generate a keypair.'
294 }
304 }
295 </P>
305 </P>
306+
296 <AddressInput
307 <AddressInput
297 className='mono mt-8'
308 className='mono mt-8'
298 prop-size='lg'
309 prop-size='lg'
@@ -307,9 +318,9 @@ class SetProxy extends React.Component {
307 <Anchor
318 <Anchor
308 className={'mt-1'}
319 className={'mt-1'}
309 prop-size={'sm'}
320 prop-size={'sm'}
310− disabled={!isValidAddress(state.proxyAddress)}
321+ prop-disabled={!isValidAddress(state.proxyAddress) || !esvisible}
311 target={'_blank'}
322 target={'_blank'}
312− href={`https://etherscan.io/address/${state.proxyAddress}`}>
323+ href={`https://${esdomain}/address/${state.proxyAddress}`}>
313 {'View on Etherscan ↗'}
324 {'View on Etherscan ↗'}
314 </Anchor>
325 </Anchor>
315
326
@@ -320,6 +331,7 @@ class SetProxy extends React.Component {
320 wallet={props.wallet}
331 wallet={props.wallet}
321 walletType={props.walletType}
332 walletType={props.walletType}
322 walletHdPath={props.walletHdPath}
333 walletHdPath={props.walletHdPath}
334+ networkType={props.networkType}
323 // Tx
335 // Tx
324 txn={state.txn}
336 txn={state.txn}
325 stx={state.stx}
337 stx={state.stx}
src/views/Transfer.js+14−2
@@ -8,6 +8,7 @@ import { AddressInput, Warning, H3 } 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'
11+import { NETWORK_NAMES } from '../lib/network'
11import { ROUTE_NAMES } from '../lib/router'
12import { ROUTE_NAMES } from '../lib/router'
12
13
13import {
14import {
@@ -250,6 +251,15 @@ class Transfer extends React.Component {
250 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
251 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
251 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
252 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
252
253
254+ const esvisible =
255+ props.networkType === NETWORK_NAMES.ROPSTEN ||
256+ props.networkType === NETWORK_NAMES.MAINNET
257+
258+ const esdomain =
259+ props.networkType === NETWORK_NAMES.ROPSTEN
260+ ? "ropsten.etherscan.io"
261+ : "etherscan.io"
262+
253 return (
263 return (
254 <Row>
264 <Row>
255 <Col>
265 <Col>
@@ -265,6 +275,7 @@ class Transfer extends React.Component {
265 You may cancel the transfer until the transfer is accepted.`
275 You may cancel the transfer until the transfer is accepted.`
266 }
276 }
267 </P>
277 </P>
278+
268 <AddressInput
279 <AddressInput
269 className={'mono mt-8'}
280 className={'mono mt-8'}
270 prop-size='lg'
281 prop-size='lg'
@@ -279,9 +290,9 @@ class Transfer extends React.Component {
279 <Anchor
290 <Anchor
280 className={'mt-1'}
291 className={'mt-1'}
281 prop-size={'s'}
292 prop-size={'s'}
282− disabled={!isValidAddress(state.receivingAddress)}
293+ prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
283 target={'_blank'}
294 target={'_blank'}
284− href={`https://etherscan.io/address/${state.receivingAddress}`}>
295+ href={`https://${esdomain}/address/${state.receivingAddress}`}>
285 {'View on Etherscan ↗'}
296 {'View on Etherscan ↗'}
286 </Anchor>
297 </Anchor>
287
298
@@ -292,6 +303,7 @@ class Transfer extends React.Component {
292 wallet={props.wallet}
303 wallet={props.wallet}
293 walletType={props.walletType}
304 walletType={props.walletType}
294 walletHdPath={props.walletHdPath}
305 walletHdPath={props.walletHdPath}
306+ networkType={props.networkType}
295 // Tx
307 // Tx
296 txn={state.txn}
308 txn={state.txn}
297 stx={state.stx}
309 stx={state.stx}