on 23dbfee help sign in
bridge/src/views/Transfer.js
359 lines 8.7 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 showGasDetails: false,
43 chainId: '',
44 gasLimit: '600000',
45 txn: Maybe.Nothing(),
46 stx: Maybe.Nothing(),
47 }
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 }
62 toggleGasDetails() {
63 this.setState({
64 showGasDetails: !this.state.showGasDetails
65 })
66 }
68 componentDidMount() {
69 const { props } = this
71 const addr = props.wallet.matchWith({
72 Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
73 Nothing: () => {
74 throw BRIDGE_ERROR.MISSING_WALLET
75 }
76 })
78 props.web3.matchWith({
79 Nothing: () => {},
80 Just: (w3) => {
81 const validWeb3 = w3.value
83 const getTxMetadata = [
84 validWeb3.eth.getTransactionCount(addr),
85 validWeb3.eth.net.getId(),
86 validWeb3.eth.getGasPrice()
87 ];
89 Promise.all(getTxMetadata).then(r => {
90 const txMetadata = {
91 nonce: r[0],
92 chainId: r[1],
93 gasPrice: fromWei(r[2], 'gwei'),
94 };
96 this.setState({...txMetadata})
98 })
99 }
100 });
102 }
106 handleAddressInput(receivingAddress) {
107 this.setState({ receivingAddress })
108 this.handleClearTxn()
109 }
113 handleConfirmAvailability() {
114 this.confirmPointAvailability().then(r => {
115 this.setState({ isAvailable: r })
116 })
117 }
121 handleCreateUnsignedTxn() {
122 const txn = this.createUnsignedTxn()
123 this.setState({ txn })
124 }
127 handleSetUserApproval(){
128 const {state} = this
129 this.setState({ userApproval: !state.userApproval })
130 }
134 handleSetTxn(txn){
135 this.setState({ txn })
136 }
140 handleSetStx(stx){
141 this.setState({
142 stx,
143 userApproval: false,
144 })
145 }
149 handleSetNonce(nonce){
150 this.setState({ nonce })
151 this.handleClearStx()
152 }
156 handleSetChainId(chainId){
157 this.setState({ chainId })
158 this.handleClearStx()
159 }
163 handleSetGasPrice(gasPrice){
164 this.setState({ gasPrice })
165 this.handleClearStx()
166 }
170 handleSetGasLimit(gasLimit){
171 this.setState({ gasLimit })
172 this.handleClearStx()
173 }
177 handleClearStx() {
178 this.setState({
179 userApproval: false,
180 stx: Maybe.Nothing(),
181 })
182 }
186 handleClearTxn() {
187 this.setState({
188 userApproval: false,
189 txn: Maybe.Nothing(),
190 stx: Maybe.Nothing(),
191 })
192 }
196 handleClearTransaction() {
197 this.setState({
198 userApproval: false,
199 txn: Maybe.Nothing(),
200 stx: Maybe.Nothing(),
201 })
202 }
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 }
221 createUnsignedTxn() {
222 const { state, props } = this
224 const validContracts = props.contracts.matchWith({
225 Just: (cs) => cs.value,
226 Nothing: () => {
227 throw BRIDGE_ERROR.MISSING_CONTRACTS
228 }
229 })
231 const validPoint = props.pointCursor.matchWith({
232 Just: (shp) => shp.value,
233 Nothing: () => {
234 throw BRIDGE_ERROR.MISSING_POINTs
235 }
236 })
238 const txn = azimuth.ecliptic.setTransferProxy(
239 validContracts,
240 validPoint,
241 state.receivingAddress
242 )
244 return Maybe.Just(txn)
245 }
249 render() {
250 const { props, state } = this
252 const validAddress = isValidAddress(state.receivingAddress)
254 const canGenerate = validAddress === true
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
260 const esvisible =
261 props.networkType === NETWORK_NAMES.ROPSTEN ||
262 props.networkType === NETWORK_NAMES.MAINNET
264 const esdomain =
265 props.networkType === NETWORK_NAMES.ROPSTEN
266 ? "ropsten.etherscan.io"
267 : "etherscan.io"
269 return (
270 <Row>
271 <Col>
272 <H1>
273 { 'Transfer'} <code>{ ` ${ob.patp(state.issuingPoint)} ` }</code>
274 { 'To a New Owner' }
275 </H1>
277 <P>
278 {
279 `Please provide the Ethereum address of the new owner. You own these
280 points until the recipient accepts the incoming transfer.
281 You may cancel the transfer until the transfer is accepted.`
282 }
283 </P>
285 <AddressInput
286 className={'mono mt-8'}
287 prop-size='lg'
288 prop-format='innerLabel'
289 value={ state.receivingAddress }
290 placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
291 onChange={ v => this.handleAddressInput(v) }>
292 <InnerLabel>{ 'New ownership address' }</InnerLabel>
293 <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
294 </AddressInput>
296 <Anchor
297 className={'mt-1'}
298 prop-size={'s'}
299 prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
300 target={'_blank'}
301 href={`https://${esdomain}/address/${state.receivingAddress}`}>
302 {'View on Etherscan ↗'}
303 </Anchor>
305 <StatelessTransaction
306 // Upper scope
307 web3={props.web3}
308 contracts={props.contracts}
309 wallet={props.wallet}
310 walletType={props.walletType}
311 walletHdPath={props.walletHdPath}
312 networkType={props.networkType}
313 // Tx
314 txn={state.txn}
315 stx={state.stx}
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
324 userApproval={state.userApproval}
325 canGenerate={ canGenerate }
326 canSign={ canSign }
327 canApprove={ canApprove }
328 canSend={ canSend }
329 // Methods
330 createUnsignedTxn={this.handleCreateUnsignedTxn}
331 setUserApproval={this.handleSetUserApproval}
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} />
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 }
353 </Col>
354 </Row>
355 )
356 }
359export default Transfer