on 616e621 help sign in
bridge/src/views/Transfer.js
342 lines 8.3 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, Form, Warning, H3 } from '../components/Base'
8import StatelessTransaction from '../components/StatelessTransaction'
10import { BRIDGE_ERROR } from '../lib/error'
11import { ROUTE_NAMES } from '../lib/router'
13import {
14 sendSignedTransaction,
15 fromWei,
16 } from '../lib/txn'
18 import {
19 isValidAddress,
20 addressFromSecp256k1Public
21 } from '../lib/wallet'
23class Transfer extends React.Component {
24 constructor(props) {
25 super(props)
27 const issuingPoint = props.pointCursor.matchWith({
28 Just: (pt) => pt.value,
29 Nothing: () => {
30 throw BRIDGE_ERROR.MISSING_POINT
31 }
32 })
34 this.state = {
35 txError: Maybe.Nothing(),
36 receivingAddress: '',
37 issuingPoint: issuingPoint,
38 userApproval: false,
39 nonce: '',
40 gasPrice: '5',
41 chainId: '',
42 gasLimit: '600000',
43 txn: Maybe.Nothing(),
44 stx: Maybe.Nothing(),
45 }
46 this.handleAddressInput = this.handleAddressInput.bind(this)
47 // Transaction
48 this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
49 this.handleSubmit = this.handleSubmit.bind(this)
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 }
60 componentDidMount() {
61 const { props } = this
63 const addr = props.wallet.matchWith({
64 Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
65 Nothing: () => {
66 throw BRIDGE_ERROR.MISSING_WALLET
67 }
68 })
70 props.web3.matchWith({
71 Nothing: () => {},
72 Just: (w3) => {
73 const validWeb3 = w3.value
75 const getTxMetadata = [
76 validWeb3.eth.getTransactionCount(addr),
77 validWeb3.eth.net.getId(),
78 validWeb3.eth.getGasPrice()
79 ];
81 Promise.all(getTxMetadata).then(r => {
82 const txMetadata = {
83 nonce: r[0],
84 chainId: r[1],
85 gasPrice: fromWei(r[2], 'gwei'),
86 };
88 this.setState({...txMetadata})
90 })
91 }
92 });
94 }
98 handleAddressInput(receivingAddress) {
99 this.setState({ receivingAddress })
100 this.handleClearTxn()
101 }
105 handleConfirmAvailability() {
106 this.confirmPointAvailability().then(r => {
107 this.setState({ isAvailable: r })
108 })
109 }
113 handleCreateUnsignedTxn() {
114 const txn = this.createUnsignedTxn()
115 this.setState({ txn })
116 }
119 handleSetUserApproval(){
120 const {state} = this
121 this.setState({ userApproval: !state.userApproval })
122 }
126 handleSetTxn(txn){
127 this.setState({ txn })
128 }
132 handleSetStx(stx){
133 this.setState({
134 stx,
135 userApproval: false,
136 })
137 }
141 handleSetNonce(nonce){
142 this.setState({ nonce })
143 this.handleClearStx()
144 }
148 handleSetChainId(chainId){
149 this.setState({ chainId })
150 this.handleClearStx()
151 }
155 handleSetGasPrice(gasPrice){
156 this.setState({ gasPrice })
157 this.handleClearStx()
158 }
162 handleSetGasLimit(gasLimit){
163 this.setState({ gasLimit })
164 this.handleClearStx()
165 }
169 handleClearStx() {
170 this.setState({
171 userApproval: false,
172 stx: Maybe.Nothing(),
173 })
174 }
178 handleClearTxn() {
179 this.setState({
180 userApproval: false,
181 txn: Maybe.Nothing(),
182 stx: Maybe.Nothing(),
183 })
184 }
188 handleClearTransaction() {
189 this.setState({
190 userApproval: false,
191 txn: Maybe.Nothing(),
192 stx: Maybe.Nothing(),
193 })
194 }
198 handleSubmit(){
199 const { props, state } = this
200 sendSignedTransaction(props.web3.value, state.stx)
201 .then(sent => {
202 props.setTxnCursor(sent)
203 props.popRoute()
204 props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
205 })
206 .catch(err => {
207 // Note that value.value is due to wrapped Maybe.Just + Result.Error
208 this.setState({ txError: Maybe.Just(err.value.value) })
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 return (
254 <Row>
255 <Col>
256 <H1>
257 { 'Transfer'} <code>{ ` ${ob.patp(state.issuingPoint)} ` }</code>
258 { 'To a New Owner' }
259 </H1>
261 <P>
262 {
263 `Please provide the Ethereum address of the new owner. You own these
264 points until the recipient accepts the incoming transfer.
265 You may cancel the transfer until the transfer is accepted.`
266 }
267 </P>
268 <Form>
269 <AddressInput
270 className={'mono mt-8'}
271 prop-size='lg'
272 prop-format='innerLabel'
273 value={ state.receivingAddress }
274 placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
275 onChange={ v => this.handleAddressInput(v) }>
276 <InnerLabel>{ 'New ownership address' }</InnerLabel>
277 <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
278 </AddressInput>
280 <Anchor
281 className={'mt-1'}
282 prop-size={'s'}
283 disabled={!isValidAddress(state.receivingAddress)}
284 target={'_blank'}
285 href={`https://etherscan.io/address/${state.receivingAddress}`}>
286 {'View on Etherscan ↗'}
287 </Anchor>
289 <StatelessTransaction
290 // Upper scope
291 web3={props.web3}
292 contracts={props.contracts}
293 wallet={props.wallet}
294 walletType={props.walletType}
295 walletHdPath={props.walletHdPath}
296 // Tx
297 txn={state.txn}
298 stx={state.stx}
299 // Tx details
300 nonce={state.nonce}
301 gasPrice={state.gasPrice}
302 chainId={state.chainId}
303 gasLimit={state.gasLimit}
304 // Checks
305 userApproval={state.userApproval}
306 canGenerate={ canGenerate }
307 canSign={ canSign }
308 canApprove={ canApprove }
309 canSend={ canSend }
310 // Methods
311 createUnsignedTxn={this.handleCreateUnsignedTxn}
312 setUserApproval={this.handleSetUserApproval}
313 setTxn={this.handleSetTxn}
314 setStx={this.handleSetStx}
315 setNonce={this.handleSetNonce}
316 setChainId={this.handleSetChainId}
317 setGasPrice={this.handleSetGasPrice}
318 setGasLimit={this.handleSetGasLimit}
319 handleSubmit={this.handleSubmit} />
321 </Form>
323 {
324 Maybe.Nothing.hasInstance(state.txError)
325 ? ''
326 : <Warning className={'mt-8'}>
327 <H3 style={{marginTop: 0, paddingTop: 0}}>
328 {
329 'There was an error sending your transaction.'
330 }
331 </H3>
332 { state.txError.value }
333 </Warning>
334 }
336 </Col>
337 </Row>
338 )
339 }
342export default Transfer