on 69dc015 help sign in
bridge/src/views/Transfer.js
350 lines 8.5 kB history raw
1import Maybe from 'folktale/maybe'
2import React from 'react'
3import * as azimuth from 'azimuth-js'
4import * as ob from 'urbit-ob'
6import { Row, Col, H1, InnerLabel, ShowBlockie, P, Anchor } from '../components/Base'
7import { AddressInput, Warning, H3 } from '../components/Base'
8import StatelessTransaction from '../components/StatelessTransaction'
10import { BRIDGE_ERROR } from '../lib/error'
11import { NETWORK_NAMES } from '../lib/network'
12import { ROUTE_NAMES } from '../lib/router'
14import {
15 sendSignedTransaction,
16 fromWei,
17 } from '../lib/txn'
19 import {
20 isValidAddress,
21 addressFromSecp256k1Public
22 } from '../lib/wallet'
24class Transfer extends React.Component {
25 constructor(props) {
26 super(props)
28 const issuingPoint = props.pointCursor.matchWith({
29 Just: (pt) => pt.value,
30 Nothing: () => {
31 throw BRIDGE_ERROR.MISSING_POINT
32 }
33 })
35 this.state = {
36 txError: Maybe.Nothing(),
37 receivingAddress: '',
38 issuingPoint: issuingPoint,
39 userApproval: false,
40 nonce: '',
41 gasPrice: '5',
42 chainId: '',
43 gasLimit: '600000',
44 txn: Maybe.Nothing(),
45 stx: Maybe.Nothing(),
46 }
47 this.handleAddressInput = this.handleAddressInput.bind(this)
48 // Transaction
49 this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
50 this.handleSubmit = this.handleSubmit.bind(this)
51 this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
52 this.handleSetTxn = this.handleSetTxn.bind(this)
53 this.handleSetStx = this.handleSetStx.bind(this)
54 this.handleSetNonce = this.handleSetNonce.bind(this)
55 this.handleSetChainId = this.handleSetChainId.bind(this)
56 this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
57 this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
58 }
61 componentDidMount() {
62 const { props } = this
64 const addr = props.wallet.matchWith({
65 Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
66 Nothing: () => {
67 throw BRIDGE_ERROR.MISSING_WALLET
68 }
69 })
71 props.web3.matchWith({
72 Nothing: () => {},
73 Just: (w3) => {
74 const validWeb3 = w3.value
76 const getTxMetadata = [
77 validWeb3.eth.getTransactionCount(addr),
78 validWeb3.eth.net.getId(),
79 validWeb3.eth.getGasPrice()
80 ];
82 Promise.all(getTxMetadata).then(r => {
83 const txMetadata = {
84 nonce: r[0],
85 chainId: r[1],
86 gasPrice: fromWei(r[2], 'gwei'),
87 };
89 this.setState({...txMetadata})
91 })
92 }
93 });
95 }
99 handleAddressInput(receivingAddress) {
100 this.setState({ receivingAddress })
101 this.handleClearTxn()
102 }
106 handleConfirmAvailability() {
107 this.confirmPointAvailability().then(r => {
108 this.setState({ isAvailable: r })
109 })
110 }
114 handleCreateUnsignedTxn() {
115 const txn = this.createUnsignedTxn()
116 this.setState({ txn })
117 }
120 handleSetUserApproval(){
121 const {state} = this
122 this.setState({ userApproval: !state.userApproval })
123 }
127 handleSetTxn(txn){
128 this.setState({ txn })
129 }
133 handleSetStx(stx){
134 this.setState({
135 stx,
136 userApproval: false,
137 })
138 }
142 handleSetNonce(nonce){
143 this.setState({ nonce })
144 this.handleClearStx()
145 }
149 handleSetChainId(chainId){
150 this.setState({ chainId })
151 this.handleClearStx()
152 }
156 handleSetGasPrice(gasPrice){
157 this.setState({ gasPrice })
158 this.handleClearStx()
159 }
163 handleSetGasLimit(gasLimit){
164 this.setState({ gasLimit })
165 this.handleClearStx()
166 }
170 handleClearStx() {
171 this.setState({
172 userApproval: false,
173 stx: Maybe.Nothing(),
174 })
175 }
179 handleClearTxn() {
180 this.setState({
181 userApproval: false,
182 txn: Maybe.Nothing(),
183 stx: Maybe.Nothing(),
184 })
185 }
189 handleClearTransaction() {
190 this.setState({
191 userApproval: false,
192 txn: Maybe.Nothing(),
193 stx: Maybe.Nothing(),
194 })
195 }
199 handleSubmit(){
200 const { props, state } = this
201 sendSignedTransaction(props.web3.value, state.stx)
202 .then(sent => {
203 props.setTxnHashCursor(sent)
204 props.popRoute()
205 props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
206 })
207 .catch(err => {
208 this.setState({ txError: err.map(val => val.merge()) })
209 })
210 }
214 createUnsignedTxn() {
215 const { state, props } = this
217 const validContracts = props.contracts.matchWith({
218 Just: (cs) => cs.value,
219 Nothing: () => {
220 throw BRIDGE_ERROR.MISSING_CONTRACTS
221 }
222 })
224 const validPoint = props.pointCursor.matchWith({
225 Just: (shp) => shp.value,
226 Nothing: () => {
227 throw BRIDGE_ERROR.MISSING_POINTs
228 }
229 })
231 const txn = azimuth.ecliptic.setTransferProxy(
232 validContracts,
233 validPoint,
234 state.receivingAddress
235 )
237 return Maybe.Just(txn)
238 }
242 render() {
243 const { props, state } = this
245 const validAddress = isValidAddress(state.receivingAddress)
247 const canGenerate = validAddress === true
249 const canSign = !Maybe.Nothing.hasInstance(state.txn)
250 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
251 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
253 const esvisible =
254 props.networkType === NETWORK_NAMES.ROPSTEN ||
255 props.networkType === NETWORK_NAMES.MAINNET
257 const esdomain =
258 props.networkType === NETWORK_NAMES.ROPSTEN
259 ? "ropsten.etherscan.io"
260 : "etherscan.io"
262 return (
263 <Row>
264 <Col>
265 <H1>
266 { 'Transfer'} <code>{ ` ${ob.patp(state.issuingPoint)} ` }</code>
267 { 'To a New Owner' }
268 </H1>
270 <P>
271 {
272 `Please provide the Ethereum address of the new owner. You own these
273 points until the recipient accepts the incoming transfer.
274 You may cancel the transfer until the transfer is accepted.`
275 }
276 </P>
278 <AddressInput
279 className={'mono mt-8'}
280 prop-size='lg'
281 prop-format='innerLabel'
282 value={ state.receivingAddress }
283 placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
284 onChange={ v => this.handleAddressInput(v) }>
285 <InnerLabel>{ 'New ownership address' }</InnerLabel>
286 <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
287 </AddressInput>
289 <Anchor
290 className={'mt-1'}
291 prop-size={'s'}
292 prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
293 target={'_blank'}
294 href={`https://${esdomain}/address/${state.receivingAddress}`}>
295 {'View on Etherscan ↗'}
296 </Anchor>
298 <StatelessTransaction
299 // Upper scope
300 web3={props.web3}
301 contracts={props.contracts}
302 wallet={props.wallet}
303 walletType={props.walletType}
304 walletHdPath={props.walletHdPath}
305 networkType={props.networkType}
306 // Tx
307 txn={state.txn}
308 stx={state.stx}
309 // Tx details
310 nonce={state.nonce}
311 gasPrice={state.gasPrice}
312 chainId={state.chainId}
313 gasLimit={state.gasLimit}
314 // Checks
315 userApproval={state.userApproval}
316 canGenerate={ canGenerate }
317 canSign={ canSign }
318 canApprove={ canApprove }
319 canSend={ canSend }
320 // Methods
321 createUnsignedTxn={this.handleCreateUnsignedTxn}
322 setUserApproval={this.handleSetUserApproval}
323 setTxn={this.handleSetTxn}
324 setStx={this.handleSetStx}
325 setNonce={this.handleSetNonce}
326 setChainId={this.handleSetChainId}
327 setGasPrice={this.handleSetGasPrice}
328 setGasLimit={this.handleSetGasLimit}
329 handleSubmit={this.handleSubmit} />
331 {
332 Maybe.Nothing.hasInstance(state.txError)
333 ? ''
334 : <Warning className={'mt-8'}>
335 <H3 style={{marginTop: 0, paddingTop: 0}}>
336 {
337 'There was an error sending your transaction.'
338 }
339 </H3>
340 { state.txError.value }
341 </Warning>
342 }
344 </Col>
345 </Row>
346 )
347 }
350export default Transfer