on 0dcd5e8 help sign in
bridge/src/views/Transfer.js
351 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.setTxnCursor(sent)
204 props.popRoute()
205 props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
206 })
207 .catch(err => {
208 // Note that value.value is due to wrapped Maybe.Just + Result.Error
209 this.setState({ txError: Maybe.Just(err.value.value) })
210 })
211 }
215 createUnsignedTxn() {
216 const { state, props } = this
218 const validContracts = props.contracts.matchWith({
219 Just: (cs) => cs.value,
220 Nothing: () => {
221 throw BRIDGE_ERROR.MISSING_CONTRACTS
222 }
223 })
225 const validPoint = props.pointCursor.matchWith({
226 Just: (shp) => shp.value,
227 Nothing: () => {
228 throw BRIDGE_ERROR.MISSING_POINTs
229 }
230 })
232 const txn = azimuth.ecliptic.setTransferProxy(
233 validContracts,
234 validPoint,
235 state.receivingAddress
236 )
238 return Maybe.Just(txn)
239 }
243 render() {
244 const { props, state } = this
246 const validAddress = isValidAddress(state.receivingAddress)
248 const canGenerate = validAddress === true
250 const canSign = !Maybe.Nothing.hasInstance(state.txn)
251 const canApprove = !Maybe.Nothing.hasInstance(state.stx)
252 const canSend = !Maybe.Nothing.hasInstance(state.stx) && state.userApproval === true
254 const esvisible =
255 props.networkType === NETWORK_NAMES.ROPSTEN ||
256 props.networkType === NETWORK_NAMES.MAINNET
258 const esdomain =
259 props.networkType === NETWORK_NAMES.ROPSTEN
260 ? "ropsten.etherscan.io"
261 : "etherscan.io"
263 return (
264 <Row>
265 <Col>
266 <H1>
267 { 'Transfer'} <code>{ ` ${ob.patp(state.issuingPoint)} ` }</code>
268 { 'To a New Owner' }
269 </H1>
271 <P>
272 {
273 `Please provide the Ethereum address of the new owner. You own these
274 points until the recipient accepts the incoming transfer.
275 You may cancel the transfer until the transfer is accepted.`
276 }
277 </P>
279 <AddressInput
280 className={'mono mt-8'}
281 prop-size='lg'
282 prop-format='innerLabel'
283 value={ state.receivingAddress }
284 placeholder={ `e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f` }
285 onChange={ v => this.handleAddressInput(v) }>
286 <InnerLabel>{ 'New ownership address' }</InnerLabel>
287 <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
288 </AddressInput>
290 <Anchor
291 className={'mt-1'}
292 prop-size={'s'}
293 prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
294 target={'_blank'}
295 href={`https://${esdomain}/address/${state.receivingAddress}`}>
296 {'View on Etherscan ↗'}
297 </Anchor>
299 <StatelessTransaction
300 // Upper scope
301 web3={props.web3}
302 contracts={props.contracts}
303 wallet={props.wallet}
304 walletType={props.walletType}
305 walletHdPath={props.walletHdPath}
306 networkType={props.networkType}
307 // Tx
308 txn={state.txn}
309 stx={state.stx}
310 // Tx details
311 nonce={state.nonce}
312 gasPrice={state.gasPrice}
313 chainId={state.chainId}
314 gasLimit={state.gasLimit}
315 // Checks
316 userApproval={state.userApproval}
317 canGenerate={ canGenerate }
318 canSign={ canSign }
319 canApprove={ canApprove }
320 canSend={ canSend }
321 // Methods
322 createUnsignedTxn={this.handleCreateUnsignedTxn}
323 setUserApproval={this.handleSetUserApproval}
324 setTxn={this.handleSetTxn}
325 setStx={this.handleSetStx}
326 setNonce={this.handleSetNonce}
327 setChainId={this.handleSetChainId}
328 setGasPrice={this.handleSetGasPrice}
329 setGasLimit={this.handleSetGasLimit}
330 handleSubmit={this.handleSubmit} />
332 {
333 Maybe.Nothing.hasInstance(state.txError)
334 ? ''
335 : <Warning className={'mt-8'}>
336 <H3 style={{marginTop: 0, paddingTop: 0}}>
337 {
338 'There was an error sending your transaction.'
339 }
340 </H3>
341 { state.txError.value }
342 </Warning>
343 }
345 </Col>
346 </Row>
347 )
348 }
351export default Transfer