on a8a4e1e help sign in
bridge/src/views/AcceptTransfer.js
338 lines 8.3 kB history raw
1import { Just, Nothing } from 'folktale/maybe'
2import React from 'react'
3import { Row, Col, H1, H3, P, Warning } from '../components/Base'
4import { InnerLabel, AddressInput, ShowBlockie, Anchor } from '../components/Base'
5import * as azimuth from 'azimuth-js'
6import * as ob from 'urbit-ob'
8import StatelessTransaction from '../components/StatelessTransaction'
9import { BRIDGE_ERROR } from '../lib/error'
10import { NETWORK_NAMES } from '../lib/network'
11import { ROUTE_NAMES } from '../lib/router'
13import {
14 sendSignedTransaction,
15 fromWei,
16 } from '../lib/txn'
18import {
19 isValidAddress,
20 addressFromSecp256k1Public,
21} from '../lib/wallet'
24class AcceptTransfer extends React.Component {
25 constructor(props) {
26 super(props)
28 const receivingAddress = props.wallet.matchWith({
29 Just: (wal) => addressFromSecp256k1Public(wal.value.publicKey),
30 Nothing: () => {
31 throw BRIDGE_ERROR.MISSING_WALLET
32 }
33 })
35 const incomingPoint = props.pointCursor.matchWith({
36 Just: (shp) => shp.value,
37 Nothing: () => {
38 throw BRIDGE_ERROR.MISSING_POINT
39 }
40 })
42 this.state = {
43 receivingAddress: receivingAddress,
44 txn: Nothing(),
45 txError: Nothing(),
46 incomingPoint: incomingPoint,
47 userApproval: false,
48 nonce: '',
49 gasPrice: '5',
50 chainId: '',
51 gasLimit: '600000',
52 stx: Nothing(),
53 }
54 this.handleAddressInput = this.handleAddressInput.bind(this)
55 // Transaction
56 this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
57 this.handleSubmit = this.handleSubmit.bind(this)
58 this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
59 this.handleSetTxn = this.handleSetTxn.bind(this)
60 this.handleSetStx = this.handleSetStx.bind(this)
61 this.handleSetNonce = this.handleSetNonce.bind(this)
62 this.handleSetChainId = this.handleSetChainId.bind(this)
63 this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
64 this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
65 }
67 componentDidMount() {
68 const { props } = this
70 const addr = props.wallet.matchWith({
71 Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
72 Nothing: () => {
73 throw BRIDGE_ERROR.MISSING_WALLET
74 }
75 })
77 props.web3.matchWith({
78 Nothing: () => {},
79 Just: (w3) => {
80 const validWeb3 = w3.value
82 const getTxMetadata = [
83 validWeb3.eth.getTransactionCount(addr),
84 validWeb3.eth.net.getId(),
85 validWeb3.eth.getGasPrice()
86 ];
88 Promise.all(getTxMetadata).then(r => {
89 const txMetadata = {
90 nonce: r[0],
91 chainId: r[1],
92 gasPrice: fromWei(r[2], 'gwei'),
93 };
95 this.setState({...txMetadata})
97 })
98 }
99 });
100 }
104 handleAddressInput(proxyAddress) {
105 this.setState({ proxyAddress })
106 this.handleClearTxn()
107 }
110 handleCreateUnsignedTxn() {
111 const txn = this.createUnsignedTxn()
112 this.setState({ txn })
113 }
116 handleSetUserApproval(){
117 const {state} = this
118 this.setState({ userApproval: !state.userApproval })
119 }
123 handleSetTxn(txn){
124 this.setState({ txn })
125 }
129 handleSetStx(stx){
130 this.setState({
131 stx,
132 userApproval: false,
133 })
134 }
138 handleSetNonce(nonce){
139 this.setState({ nonce })
140 this.handleClearStx()
141 }
145 handleSetChainId(chainId){
146 this.setState({ chainId })
147 this.handleClearStx()
148 }
152 handleSetGasPrice(gasPrice){
153 this.setState({ gasPrice })
154 this.handleClearStx()
155 }
159 handleSetGasLimit(gasLimit){
160 this.setState({ gasLimit })
161 this.handleClearStx()
162 }
166 handleClearStx() {
167 this.setState({
168 userApproval: false,
169 stx: Nothing(),
170 })
171 }
175 handleClearTxn() {
176 this.setState({
177 userApproval: false,
178 txn: Nothing(),
179 stx: Nothing(),
180 })
181 }
185 handleClearTransaction() {
186 this.setState({
187 userApproval: false,
188 txn: Nothing(),
189 stx: Nothing(),
190 })
191 }
195 handleSubmit() {
196 const { props, state } = this
197 sendSignedTransaction(props.web3.value, state.stx)
198 .then(sent => {
199 props.setTxnCursor(sent)
200 props.popRoute()
201 props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
202 })
203 .catch(err => {
204 this.setState({ txError: err.map(val => val.merge()) })
205 })
206 }
208 createUnsignedTxn() {
209 const { state, props } = this
211 const validContracts = props.contracts.matchWith({
212 Just: (cs) => cs.value,
213 Nothing: () => {
214 throw BRIDGE_ERROR.MISSING_CONTRACTS
215 }
216 })
218 const validPoint = props.pointCursor.matchWith({
219 Just: (shp) => shp.value,
220 Nothing: () => {
221 throw BRIDGE_ERROR.MISSING_POINT
222 }
223 })
225 const owner = props.pointCache[validPoint].owner
227 return Just(azimuth.ecliptic.transferFrom(
228 validContracts,
229 owner,
230 state.receivingAddress,
231 validPoint
232 ))
233 }
235 render() {
237 const { state, props } = this
238 const validAddress = isValidAddress(state.receivingAddress)
239 const canGenerate = validAddress === true
240 const canSign = Just.hasInstance(state.txn)
241 const canApprove = Just.hasInstance(state.stx)
242 const canSend =
243 Just.hasInstance(state.stx)
244 && state.userApproval === true
246 const esvisible =
247 props.networkType === NETWORK_NAMES.ROPSTEN ||
248 props.networkType === NETWORK_NAMES.MAINNET
250 const esdomain =
251 props.networkType === NETWORK_NAMES.ROPSTEN
252 ? "ropsten.etherscan.io"
253 : "etherscan.io"
255 return (
256 <Row>
257 <Col>
258 <H1>
259 { 'Accept Transfer of '} <code>{ ` ${ob.patp(state.incomingPoint)} ` }</code>
260 </H1>
262 <P>
263 {
264 "By default, the recipient is the address you're logged in " +
265 "as. But you can transfer to any address you like."
266 }
267 </P>
268 <AddressInput
269 className='mono mt-8'
270 prop-size='lg'
271 prop-format='innerLabel'
272 value={ state.receivingAddress }
273 onChange={ v => this.handleAddressInput(v) }>
274 <InnerLabel>{ 'Receiving Address' }</InnerLabel>
275 <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
276 </AddressInput>
278 <Anchor
279 className={'mt-1 sm'}
280 prop-size='sm'
281 prop-disabled={!isValidAddress(state.receivingAddress) || !esvisible}
282 target={'_blank'}
283 href={`https://${esdomain}/address/${state.receivingAddress}`}>
284 {'View on Etherscan ↗'}
285 </Anchor>
287 <StatelessTransaction
288 // Upper scope
289 web3={props.web3}
290 contracts={props.contracts}
291 wallet={props.wallet}
292 walletType={props.walletType}
293 walletHdPath={props.walletHdPath}
294 networkType={props.networkType}
295 // Tx
296 txn={state.txn}
297 stx={state.stx}
298 // Tx details
299 nonce={state.nonce}
300 gasPrice={state.gasPrice}
301 chainId={state.chainId}
302 gasLimit={state.gasLimit}
303 // Checks
304 userApproval={state.userApproval}
305 canGenerate={ canGenerate }
306 canSign={ canSign }
307 canApprove={ canApprove }
308 canSend={ canSend }
309 // Methods
310 createUnsignedTxn={this.handleCreateUnsignedTxn}
311 setUserApproval={this.handleSetUserApproval}
312 setTxn={this.handleSetTxn}
313 setStx={this.handleSetStx}
314 setNonce={this.handleSetNonce}
315 setChainId={this.handleSetChainId}
316 setGasPrice={this.handleSetGasPrice}
317 setGasLimit={this.handleSetGasLimit}
318 handleSubmit={this.handleSubmit} />
320 {
321 Nothing.hasInstance(state.txError)
322 ? ''
323 : <Warning className={'mt-8'}>
324 <H3 style={{marginTop: 0, paddingTop: 0}}>
325 {
326 'There was an error sending your transaction.'
327 }
328 </H3>
329 { state.txError.value }
330 </Warning>
331 }
332 </Col>
333 </Row>
334 )
335 }
338export default AcceptTransfer