on master help sign in
Merge pull request #26 from urbit/jt-issue-12

Manually follow EIP-155 in certain circumstances

commit23d8ffb21ab2144a42f31621765e388569b33e5c authorWill Kim <william.jahaziel.kim@gmail.com> dateFri 1 Feb 2019, 10:54 (8 years ago) parent8689d11c879957
12 files changed+194−109
package-lock.json+29−8 package.json+2−2 src/components/StatelessTransaction.js+1 src/lib/txn.js+50−74 src/views/AcceptTransfer.js+12−2 src/views/CancelTransfer.js+1 src/views/CreateGalaxy.js+13−2 src/views/IssueChild.js+14−5 src/views/SentTransaction.js+42−12 src/views/SetKeys.js+2 src/views/SetProxy.js+14−2 src/views/Transfer.js+14−2
package-lock.json+29−8
@@ -952,17 +952,28 @@
952952 "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw=="
953953 },
954954 "@ledgerhq/hw-app-eth": {
955− "version": "4.32.0",
956− "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-4.32.0.tgz",
957− "integrity": "sha512-d22WinjcsqJNoZSI+6UpTWZ7hl+UhL2dFeVeliCwtBWSj40z6F25MpoviGxPsv0WC7IUjayw+a9jIRcOJ5kkIw==",
955+ "version": "4.35.0",
956+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-4.35.0.tgz",
957+ "integrity": "sha512-MSDr8+CaoXhtm64ELuI/8wpcfmrMUjzGJgASY6bnjc82vAW+6sHNZlTU0zWRTZxqQUuZ8WpuJP159cf92MWq3g==",
958958 "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+ }
960970 }
961971 },
962972 "@ledgerhq/hw-transport": {
963973 "version": "4.32.0",
964974 "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.32.0.tgz",
965975 "integrity": "sha512-Wgsk9UHC4RShqYoDeIEeKgHZOvNCtB0WWIG0xqlVPzS+IcKDkIxtXQw7hTA7GQSuDuGeauVtlbTQ5yat6+2/BA==",
976+ "dev": true,
966977 "requires": {
967978 "events": "^3.0.0"
968979 }
@@ -980,12 +991,22 @@
980991 }
981992 },
982993 "@ledgerhq/hw-transport-u2f": {
983− "version": "4.32.0",
984− "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.32.0.tgz",
985− "integrity": "sha512-xzY+Q7ObVVv/V/wxvp1eEbDhFf+LFnavQGvqw8EZkCEmkX4iKh2w7AH+B8k1XWS+Mpa3CiG3TVQQE85mf+6jQg==",
994+ "version": "4.35.0",
995+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.35.0.tgz",
996+ "integrity": "sha512-cVPH0SNRrIKVZz9zHvulHGPSdDQiV1hCK634KZa2M2+fMVhQxUi4YRBQz7QpI88CwRyEy+sBfTfSahPWjQUN2Q==",
986997 "requires": {
987− "@ledgerhq/hw-transport": "^4.32.0",
998+ "@ledgerhq/hw-transport": "^4.35.0",
988999 "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+ }
9891010 }
9901011 },
9911012 "@mrmlnc/readdir-enhanced": {
package.json+2−2
@@ -14,9 +14,9 @@
1414 "node": "11.0.0"
1515 },
1616 "dependencies": {
17− "@ledgerhq/hw-app-eth": "^4.24.0",
18− "@ledgerhq/hw-transport-u2f": "^4.28.0",
1917 "azimuth-js": "^0.10.0",
18+ "@ledgerhq/hw-app-eth": "^4.35.0",
19+ "@ledgerhq/hw-transport-u2f": "^4.35.0",
2020 "azimuth-solidity": "1.0.2",
2121 "babel-polyfill": "^6.26.0",
2222 "bip32": "^1.0.2",
src/components/StatelessTransaction.js+1
@@ -114,6 +114,7 @@ const StatelessTransaction = props => {
114114 wallet: props.wallet,
115115 walletType: props.walletType,
116116 walletHdPath: props.walletHdPath,
117+ networkType: props.networkType,
117118 txn: props.txn,
118119 setStx: props.setStx,
119120 nonce: props.nonce,
src/lib/txn.js+50−74
@@ -5,6 +5,7 @@ import Tx from 'ethereumjs-tx'
55import Web3 from 'web3'
66
77import { BRIDGE_ERROR } from '../lib/error'
8+import { NETWORK_NAMES } from '../lib/network'
89import { ledgerSignTransaction } from '../lib/ledger'
910import { trezorSignTransaction } from '../lib/trezor'
1011import {
@@ -12,7 +13,6 @@ import {
1213 addHexPrefix
1314 } from '../lib/wallet'
1415
15−
1616const TXN_PURPOSE = {
1717 SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
1818 SET_TRANSFER_PROXY: Symbol('SET_TRANSFER_PROXY'),
@@ -43,41 +43,13 @@ const renderTxnPurpose = (purpose) =>
4343 ? 'cancel this transfer'
4444 : 'perform this transaction'
4545
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−
7546const signTransaction = async config => {
7647
7748 let {
7849 wallet,
7950 walletType,
8051 walletHdPath,
52+ networkType,
8153 txn,
8254 setStx,
8355 nonce,
@@ -91,18 +63,62 @@ const signTransaction = async config => {
9163 gasPrice = toHex(toWei(gasPrice, 'gwei'))
9264 gasLimit = toHex(gasLimit)
9365
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+
94112 const wal = wallet.matchWith({
95113 Just: (w) => w.value,
96− Nothing: () => {
97− throw BRIDGE_ERROR.MISSING_WALLET
98− }
114+ Nothing: () => { throw BRIDGE_ERROR.MISSING_WALLET }
99115 })
100116
101117 const sec = wal.privateKey
102118
103119 const utx = txn.matchWith({
104120 Just: (tx) =>
105− Object.assign(tx.value, { nonce, chainId, gasPrice, gasLimit }),
121+ Object.assign(tx.value, signingParams),
106122 Nothing: () => {
107123 throw BRIDGE_ERROR.MISSING_TXN
108124 }
@@ -121,8 +137,6 @@ const signTransaction = async config => {
121137 setStx(Maybe.Just(stx))
122138}
123139
124−
125−
126140const sendSignedTransaction = (web3, stx) => {
127141 const txn = stx.matchWith({
128142 Just: (tx) => tx.value,
@@ -170,28 +184,6 @@ const toHex = dummy.utils.toHex
170184const toWei = dummy.utils.toWei
171185const fromWei = dummy.utils.fromWei
172186
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−
195187const canDecodePatp = p => {
196188 try {
197189 ob.patp2dec(p)
@@ -201,21 +193,6 @@ const canDecodePatp = p => {
201193 }
202194}
203195
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−
219196export {
220197 signTransaction,
221198 sendSignedTransaction,
@@ -228,5 +205,4 @@ export {
228205 toWei,
229206 fromWei,
230207 canDecodePatp,
231− // confirmShipAvailability,
232208}
src/views/AcceptTransfer.js+12−2
@@ -7,6 +7,7 @@ import * as ob from 'urbit-ob'
77
88import StatelessTransaction from '../components/StatelessTransaction'
99import { BRIDGE_ERROR } from '../lib/error'
10+import { NETWORK_NAMES } from '../lib/network'
1011import { ROUTE_NAMES } from '../lib/router'
1112
1213import {
@@ -263,6 +264,14 @@ class AcceptTransfer extends React.Component {
263264 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
264265 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
265266
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"
266275
267276 return (
268277 <Row>
@@ -290,9 +299,9 @@ class AcceptTransfer extends React.Component {
290299 <Anchor
291300 className={'mt-1 sm'}
292301 prop-size='sm'
293− prop-disabled={!isValidAddress(state.receivingAddress)}
302+ prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
294303 target={'_blank'}
295− href={`https://etherscan.io/address/${state.receivingAddress}`}>
304+ href={`https://${esdomain}/address/${state.receivingAddress}`}>
296305 {'View on Etherscan ↗'}
297306 </Anchor>
298307
@@ -303,6 +312,7 @@ class AcceptTransfer extends React.Component {
303312 wallet={props.wallet}
304313 walletType={props.walletType}
305314 walletHdPath={props.walletHdPath}
315+ networkType={props.networkType}
306316 // Tx
307317 txn={state.txn}
308318 stx={state.stx}
src/views/CancelTransfer.js+1
@@ -234,6 +234,7 @@ class CancelTransfer extends React.Component {
234234 wallet={props.wallet}
235235 walletType={props.walletType}
236236 walletHdPath={props.walletHdPath}
237+ networkType={props.networkType}
237238 // Tx
238239 txn={state.txn}
239240 stx={state.stx}
src/views/CreateGalaxy.js+13−2
@@ -10,6 +10,8 @@ import StatelessTransaction from '../components/StatelessTransaction'
1010
1111import { BRIDGE_ERROR } from '../lib/error'
1212
13+import { NETWORK_NAMES } from '../lib/network'
14+
1315import {
1416 sendSignedTransaction,
1517 getTxnInfo,
@@ -179,6 +181,14 @@ class CreateGalaxy extends React.Component {
179181 const validAddress = isValidAddress(state.galaxyOwner)
180182 const validGalaxy = isValidGalaxy(state.galaxyName)
181183
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"
182192
183193 return (
184194 <Row>
@@ -225,9 +235,9 @@ class CreateGalaxy extends React.Component {
225235 <Anchor
226236 className={'mt-1'}
227237 prop-size={'sm'}
228− prop-disabled={!validAddress}
238+ prop-disabled={!validAddress || !esvisible}
229239 target={'_blank'}
230− href={`https://etherscan.io/address/${state.galaxyOwner}`}>
240+ href={`https://${esdomain}/address/${state.galaxyOwner}`}>
231241 {'View on Etherscan ↗'}
232242 </Anchor>
233243
@@ -249,6 +259,7 @@ class CreateGalaxy extends React.Component {
249259 wallet={props.wallet}
250260 walletType={props.walletType}
251261 walletHdPath={props.walletHdPath}
262+ networkType={props.networkType}
252263 // Tx
253264 txApproval={state.txApproval}
254265 txn={state.txn}
src/views/IssueChild.js+14−5
@@ -10,6 +10,7 @@ import { PointInput, AddressInput, InnerLabel } from '../components/Base'
1010import StatelessTransaction from '../components/StatelessTransaction'
1111
1212import { ROUTE_NAMES } from '../lib/router'
13+import { NETWORK_NAMES } from '../lib/network'
1314import { BRIDGE_ERROR } from '../lib/error'
1415import { getSpawnCandidate } from '../lib/child'
1516import {
@@ -337,8 +338,6 @@ class IssueChild extends React.Component {
337338 render() {
338339 const { props, state } = this
339340
340−
341−
342341 const validAddress = isValidAddress(state.receivingAddress)
343342 const validPoint = canDecodePatp(state.desiredPoint)
344343
@@ -354,11 +353,19 @@ class IssueChild extends React.Component {
354353 }
355354 })
356355
357−
358356 const canSign = !Maybe.Nothing.hasInstance(state.txn)
359357 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
360358 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
361359
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+
362369 return (
363370 <Row>
364371 <Col>
@@ -433,9 +440,9 @@ class IssueChild extends React.Component {
433440 <Anchor
434441 className={'mt-1'}
435442 prop-size={'sm'}
436− prop-disabled={!isValidAddress(state.receivingAddress)}
443+ prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
437444 target={'_blank'}
438− href={`https://etherscan.io/address/${state.receivingAddress}`}>
445+ href={`https://${esdomain}/address/${state.receivingAddress}`}>
439446 {'View on Etherscan ↗'}
440447 </Anchor>
441448
@@ -461,6 +468,7 @@ class IssueChild extends React.Component {
461468 wallet={props.wallet}
462469 walletType={props.walletType}
463470 walletHdPath={props.walletHdPath}
471+ networkType={props.networkType}
464472 // Tx
465473 txn={state.txn}
466474 stx={state.stx}
@@ -498,6 +506,7 @@ class IssueChild extends React.Component {
498506 { state.txError.value }
499507 </Warning>
500508 }
509+
501510 </Col>
502511 </Row>
503512 )
src/views/SentTransaction.js+42−12
@@ -4,7 +4,36 @@ import { Button } from '../components/Base'
44
55import { BRIDGE_ERROR, renderTxnError } from '../lib/error'
66
7−const Success = (props) =>
7+import { NETWORK_NAMES } from '../lib/network'
8+
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 (
837 <Row>
938 <Col>
1039 <H1>{ 'Your Transaction was Sent' }</H1>
@@ -12,8 +41,7 @@ const Success = (props) =>
1241 <P>
1342 {
1443 `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
16− track of it, click the Etherscan link below.`
44+ execute, especially if the network is busy. ${esmessage}`
1745 }
1846 </P>
1947
@@ -21,15 +49,13 @@ const Success = (props) =>
2149 <P>
2250 { props.hash }
2351 </P>
24− <Anchor
25− className={'mb-4 mt-1'}
26− prop-size={'sm'}
27− target={'_blank'}
28− href={`https://etherscan.io/tx/${props.hash}`}>
29− {'View on Etherscan ↗'}
30− </Anchor>
52+
53+ { esanchor }
54+
3155 </Col>
3256 </Row>
57+ )
58+}
3359
3460const Failure = (props) =>
3561
@@ -50,7 +76,7 @@ const Failure = (props) =>
5076 </Row>
5177
5278const SentTransaction = (props) => {
53− const { web3, txnCursor, popRoute } = props
79+ const { web3, txnCursor, networkType, popRoute } = props
5480 const { setPointCursor, pointCursor } = props
5581
5682 const w3 = web3.matchWith({
@@ -65,7 +91,11 @@ const SentTransaction = (props) => {
6591
6692 const body = result.matchWith({
6793 Error: message => <Failure web3={ w3 } message={ message.value } />,
68− Ok: txn => <Success hash={ txn.value.transactionHash } />
94+ Ok: txn =>
95+ <Success
96+ hash={ txn.value.transactionHash }
97+ networkType={ networkType }
98+ />
6999 })
70100
71101 const ok =
src/views/SetKeys.js+2
@@ -329,6 +329,8 @@ class SetKeys extends React.Component {
329329 contracts={props.contracts}
330330 wallet={props.wallet}
331331 walletType={props.walletType}
332+ walletHdPath={props.walletHdPath}
333+ networkType={props.networkType}
332334 // Tx
333335 txn={state.txn}
334336 stx={state.stx}
src/views/SetProxy.js+14−2
@@ -8,6 +8,8 @@ import * as ob from 'urbit-ob'
88import { PROXY_TYPE, renderProxyType } from '../lib/proxy'
99import { BRIDGE_ERROR } from '../lib/error'
1010
11+import { NETWORK_NAMES } from '../lib/network'
12+
1113import StatelessTransaction from '../components/StatelessTransaction'
1214
1315import { ROUTE_NAMES } from '../lib/router'
@@ -274,6 +276,14 @@ class SetProxy extends React.Component {
274276 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
275277 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
276278
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"
277287
278288 const ucFirst = w => w.charAt(0).toUpperCase() + w.slice(1);
279289
@@ -293,6 +303,7 @@ class SetProxy extends React.Component {
293303 'Generator to generate a keypair.'
294304 }
295305 </P>
306+
296307 <AddressInput
297308 className='mono mt-8'
298309 prop-size='lg'
@@ -307,9 +318,9 @@ class SetProxy extends React.Component {
307318 <Anchor
308319 className={'mt-1'}
309320 prop-size={'sm'}
310− disabled={!isValidAddress(state.proxyAddress)}
321+ prop-disabled={!isValidAddress(state.proxyAddress) || !esvisible}
311322 target={'_blank'}
312− href={`https://etherscan.io/address/${state.proxyAddress}`}>
323+ href={`https://${esdomain}/address/${state.proxyAddress}`}>
313324 {'View on Etherscan ↗'}
314325 </Anchor>
315326
@@ -320,6 +331,7 @@ class SetProxy extends React.Component {
320331 wallet={props.wallet}
321332 walletType={props.walletType}
322333 walletHdPath={props.walletHdPath}
334+ networkType={props.networkType}
323335 // Tx
324336 txn={state.txn}
325337 stx={state.stx}
src/views/Transfer.js+14−2
@@ -8,6 +8,7 @@ import { AddressInput, Warning, H3 } from '../components/Base'
88import StatelessTransaction from '../components/StatelessTransaction'
99
1010import { BRIDGE_ERROR } from '../lib/error'
11+import { NETWORK_NAMES } from '../lib/network'
1112import { ROUTE_NAMES } from '../lib/router'
1213
1314import {
@@ -250,6 +251,15 @@ class Transfer extends React.Component {
250251 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
251252 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
252253
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+
253263 return (
254264 <Row>
255265 <Col>
@@ -265,6 +275,7 @@ class Transfer extends React.Component {
265275 You may cancel the transfer until the transfer is accepted.`
266276 }
267277 </P>
278+
268279 <AddressInput
269280 className={'mono mt-8'}
270281 prop-size='lg'
@@ -279,9 +290,9 @@ class Transfer extends React.Component {
279290 <Anchor
280291 className={'mt-1'}
281292 prop-size={'s'}
282− disabled={!isValidAddress(state.receivingAddress)}
293+ prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
283294 target={'_blank'}
284− href={`https://etherscan.io/address/${state.receivingAddress}`}>
295+ href={`https://${esdomain}/address/${state.receivingAddress}`}>
285296 {'View on Etherscan ↗'}
286297 </Anchor>
287298
@@ -292,6 +303,7 @@ class Transfer extends React.Component {
292303 wallet={props.wallet}
293304 walletType={props.walletType}
294305 walletHdPath={props.walletHdPath}
306+ networkType={props.networkType}
295307 // Tx
296308 txn={state.txn}
297309 stx={state.stx}