@@ -1,244 +1,426 @@
1−import { Nothing } from 'folktale/maybe'
1+import { Nothing, Just } from 'folktale/maybe'
2import React from 'react'
2import React from 'react'
4import { Code, H3 } from './Base'
4import { Code, H3 } from './Base'
5import { Button } from './Base'
5import { Button } from './Base'
6import { CheckboxButton, Input, InnerLabel } from './Base'
6import { CheckboxButton, Input, InnerLabel } from './Base'
7+import { Warning } from '../components/Base'
8−import { renderSignedTx, signTransaction } from '../lib/txn'
9+import { addressFromSecp256k1Public } from '../lib/wallet'
10+import { BRIDGE_ERROR } from '../lib/error'
11+import { ROUTE_NAMES } from '../lib/router'
14+ sendSignedTransaction,
10−const StatelessTransaction = props => {
20+class StatelessTransaction extends React.Component {
11− const { web3, gasPrice, gasLimit, nonce, chainId } = props
21+
12− const { setNonce, setChainId, setGasLimit, setGasPrice, showGasDetails, toggleGasDetails } = props
22+ constructor(props) {
28+ showGasDetails: false,
13− const { txn, stx, createUnsignedTxn } = props
37+ this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
14− const { canSign, canGenerate, canApprove } = props
38+ this.submit = this.submit.bind(this)
15− const { setUserApproval, userApproval } = props
39+ this.setUserApproval = this.setUserApproval.bind(this)
16− const { canSend, handleSubmit } = props
40+ this.setTxn = this.setTxn.bind(this)
41+ this.setStx = this.setStx.bind(this)
42+ this.sendTxn = this.sendTxn.bind(this)
43+ this.setNonce = this.setNonce.bind(this)
44+ this.setChainId = this.setChainId.bind(this)
45+ this.setGasPrice = this.setGasPrice.bind(this)
46+ this.setGasLimit = this.setGasLimit.bind(this)
47+ this.rangeChange = this.rangeChange.bind(this)
48+ this.toggleGasDetails = this.toggleGasDetails.bind(this)
51+ componentDidMount() {
52+ const { props } = this
54+ const addr = props.wallet.matchWith({
55+ Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
57+ throw BRIDGE_ERROR.MISSING_WALLET
61+ props.web3.matchWith({
64+ const validWeb3 = w3.value
66+ const getTxMetadata = [
67+ validWeb3.eth.getTransactionCount(addr),
68+ validWeb3.eth.net.getId(),
69+ validWeb3.eth.getGasPrice()
72+ Promise.all(getTxMetadata).then(r => {
76+ gasPrice: fromWei(r[2], 'gwei'),
79+ this.setState({...txMetadata})
87+ const { props, state } = this
88+ sendSignedTransaction(props.web3.value, state.stx)
90+ props.setTxnHashCursor(sent)
93+ if (props.networkSeed) {
94+ props.setNetworkSeedCache(props.networkSeed)
95+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, {promptKeyfile: true})
97+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
102+ this.setState({ txError: err.map(val => val.merge()) })
104+ this.setState({ txError: err })
110+ const {state} = this
111+ this.setState({ userApproval: !state.userApproval })
114+ toggleGasDetails() {
116+ showGasDetails: !this.state.showGasDetails
123+ userApproval: false,
128+ this.setState({ txn })
131+ createUnsignedTxn() {
132+ const txn = this.props.createUnsignedTxn()
134+ this.setState({ txn })
139+ userApproval: false,
146+ this.setState({ nonce })
150+ setChainId(chainId){
151+ this.setState({ chainId })
155+ setGasPrice(gasPrice) {
156+ this.setState({ gasPrice })
161+ this.setGasPrice(e.target.value);
164+ setGasLimit(gasLimit){
165+ this.setState({ gasLimit })
171+ userApproval: false,
18 // TODO: Investigate
176 // TODO: Investigate
19− // setState doesn't seem to work in SetProxy/handleSubmit;
177+ // setState doesn't seem to work in SetProxy/submit;
20 // - TypeError: Cannot read property 'updater' of undefined
178 // - TypeError: Cannot read property 'updater' of undefined
21 // so just modifying the DOM manually here. (https://imgur.com/a/i0Qsyq1)
179 // so just modifying the DOM manually here. (https://imgur.com/a/i0Qsyq1)
23− const handleRangeChange = (e) => {
24− setGasPrice(e.target.value);
27− const sendTxn = (e) => {
181+ sendTxn(e) {
28 e.target.setAttribute("disabled", true);
182 e.target.setAttribute("disabled", true);
29 let spinner = e.target.querySelectorAll('.btn-spinner')[0];
183 let spinner = e.target.querySelectorAll('.btn-spinner')[0];
30 spinner.classList.remove('hide');
184 spinner.classList.remove('hide');
31− handleSubmit()
185+ this.submit()
189+ const { web3, canGenerate } = this.props
190+ const { gasPrice, gasLimit, nonce, chainId,
191+ txn, stx, userApproval, showGasDetails } = this.state
193+ const { setNonce, setChainId, setGasLimit, setGasPrice, toggleGasDetails,
194+ setUserApproval, sendTxn, createUnsignedTxn } = this
196+ const { state } = this
198+ const canSign = Just.hasInstance(txn)
199+ const canApprove = Just.hasInstance(stx)
200+ const canSend = Just.hasInstance(stx) && userApproval === true
34− const generateButtonColor =
202+ const generateButtonColor =
35− Nothing.hasInstance(txn)
203+ Nothing.hasInstance(txn)
36− ? 'blue'
204+ ? 'blue'
37− : 'green'
205+ : 'green'
39− const signerButtonColor =
207+ const signerButtonColor =
40− Nothing.hasInstance(stx)
208+ Nothing.hasInstance(stx)
41− ? 'blue'
209+ ? 'blue'
42− : 'green'
210+ : 'green'
44− const generateTxnButton =
212+ const generateTxnButton =
46− className={ 'mt-8' }
214+ className={ 'mt-8' }
47− disabled={ !canGenerate }
215+ disabled={ !canGenerate }
48− prop-color={ generateButtonColor }
216+ prop-color={ generateButtonColor }
49− prop-size={ 'lg wide' }
217+ prop-size={ 'lg wide' }
50− onClick={ createUnsignedTxn }
218+ onClick={ createUnsignedTxn }
52− { 'Generate Transaction' }
220+ { 'Generate Transaction' }
53− </Button>
221+ </Button>
55− const unsignedTxnDisplay = txn.matchWith({
223+ const unsignedTxnDisplay = txn.matchWith({
56− Nothing: _ => '',
224+ Nothing: _ => '',
57− Just: tx =>
225+ Just: tx =>
227+ <H3 className={ 'mt-8' }>
228+ { 'Unsigned Transaction' }
231+ { JSON.stringify(tx.value, null, 2) }
236+ const gasPriceRangeDialogue = (
58 <React.Fragment>
237 <React.Fragment>
59− <H3 className={ 'mt-8' }>
60− { 'Unsigned Transaction' }
63− { JSON.stringify(tx.value, null, 2) }
68− const gasPriceRangeDialogue = (
70− <div className="mt-12 flex space-between align-baseline">
238+ <div className="mt-12 flex space-between align-baseline">
72− <span>Gas Price:</span>
240+ <span>Gas Price:</span>
73− <span className="ml-4 text-700 text-sm">{gasPrice} gwei</span>
241+ <span className="ml-4 text-700 text-sm">{gasPrice} gwei</span>
243+ <div className="text-sm">
244+ <span>Max transaction fee: </span>
245+ <span className="text-700">{(gasPrice * gasLimit) / 1000000000} eth</span>
75− <div className="text-sm">
250+ className="mt-4"
76− <span>Max transaction fee: </span>
251+ type="range"
77− <span className="text-700">{(gasPrice * gasLimit) / 1000000000} eth</span>
252+ min="2"
256+ onChange={this.rangeChange}
259+ <div className="flex space-between text-sm mb-8">
88− onChange={handleRangeChange}
91− <div className="flex space-between text-sm mb-8">
98− const toggleGasDetailsDialogue = (
99− <a href="javascript:void(0)" onClick={toggleGasDetails}>Gas Details</a>
102− const gasPriceDialogue =
104− className={ 'mono mt-4' }
106− prop-format={ 'innerLabel' }
108− onChange={ setGasPrice }
111− { 'Gas Price (gwei)' }
115− const gasLimitDialogue =
117− className={ 'mono mt-4' }
119− prop-format={ 'innerLabel' }
121− onChange={ setGasLimit }>
127− const nonceDialogue =
129− className={ 'mono mt-4' }
131− prop-format={ 'innerLabel' }
133− onChange={ setNonce }
140− const chainDialogue =
142− className={ 'mono mt-4 mb-8' }
144− prop-format={ 'innerLabel' }
146− onChange={ setChainId }
153− const onlineParamsDialogue = web3.matchWith({
162− const signTxnButton =
164− disabled={ !canSign }
165− className={ 'mt-8' }
166− prop-size={ 'lg wide' }
167− prop-color={ signerButtonColor }
168− onClick={ () => signTransaction(props) }
170− { 'Sign Transaction' }
173− const signedTxnDisplay = stx.matchWith({
177− <H3 className={ 'mt-8' }>
178− { 'Signed Transaction' }
181− { JSON.stringify(renderSignedTx(tx.value), null, 2) }
186− const confirmButton =
188− className={ 'mt-8' }
189− disabled={ !canApprove }
190− onClick={ setUserApproval }
191− state={ userApproval }
193− <div onClick={ setUserApproval }>
194− { `I approve this transaction and wish to send.` }
198− const sendTxnButton =
200− prop-size={ 'xl wide' }
201− className={ 'mt-8' }
202− disabled={ !canSend }
205− <span className="relative">
206− <span className="btn-spinner hide"></span>
207− { 'Send Transaction' }
211− const sendDialogue = web3.matchWith({
217 </React.Fragment>
263 </React.Fragment>
266+ const toggleGasDetailsDialogue = (
267+ <span className="text-link" onClick={toggleGasDetails}>Gas Details</span>
270+ const gasPriceDialogue =
272+ className={ 'mono mt-4' }
274+ prop-format={ 'innerLabel' }
276+ onChange={ setGasPrice }
279+ { 'Gas Price (gwei)' }
220− return (
283+ const gasLimitDialogue =
221− <React.Fragment>
284+ <Input
222− { generateTxnButton }
285+ className={ 'mono mt-4' }
223− { unsignedTxnDisplay }
286+ prop-size={ 'md' }
287+ prop-format={ 'innerLabel' }
289+ onChange={ setGasLimit }>
225− { gasPriceRangeDialogue }
295+ const nonceDialogue =
226− { toggleGasDetailsDialogue }
296+ <Input
297+ className={ 'mono mt-4' }
299+ prop-format={ 'innerLabel' }
301+ onChange={ setNonce }
228− { showGasDetails &&
308+ const chainDialogue =
310+ className={ 'mono mt-4 mb-8' }
312+ prop-format={ 'innerLabel' }
314+ onChange={ setChainId }
321+ const onlineParamsDialogue = web3.matchWith({
229− <div>
322+ Just: _ => <div />,
230− { gasPriceDialogue }
323+ Nothing: _ =>
231− { gasLimitDialogue }
324+ <React.Fragment>
330+ const signTxnButton =
332+ disabled={ !canSign }
333+ className={ 'mt-8' }
334+ prop-size={ 'lg wide' }
335+ prop-color={ signerButtonColor }
336+ onClick={ () => signTransaction({...this.props, ...this.state, setStx: this.setStx}) }
338+ { 'Sign Transaction' }
341+ const signedTxnDisplay = stx.matchWith({
345+ <H3 className={ 'mt-8' }>
346+ { 'Signed Transaction' }
349+ { JSON.stringify(renderSignedTx(tx.value), null, 2) }
354+ const confirmButton =
356+ className={ 'mt-8' }
357+ disabled={ !canApprove }
358+ onClick={ setUserApproval }
359+ state={ userApproval }
361+ <div onClick={ setUserApproval }>
362+ { `I approve this transaction and wish to send.` }
233− }
364+ </CheckboxButton>
234− { onlineParamsDialogue }
236− { signTxnButton }
366+ const sendTxnButton =
368+ prop-size={ 'xl wide' }
369+ className={ 'mt-8' }
370+ disabled={ !canSend }
373+ <span className="relative">
374+ <span className="btn-spinner hide"></span>
375+ { 'Send Transaction' }
379+ const sendDialogue = web3.matchWith({
388+ const txnErrorDialogue = Nothing.hasInstance(state.txError)
390+ : <Warning className={'mt-8'}>
391+ <H3 style={{marginTop: 0, paddingTop: 0}}>
393+ 'There was an error sending your transaction.'
396+ { state.txError.value }
401+ { generateTxnButton }
402+ { unsignedTxnDisplay }
404+ { gasPriceRangeDialogue }
405+ { toggleGasDetailsDialogue }
409+ { gasPriceDialogue }
410+ { gasLimitDialogue }
413+ { onlineParamsDialogue }
238− { signedTxnDisplay }
417+ { signedTxnDisplay }
239− { sendDialogue }
418+ { sendDialogue }
420+ { txnErrorDialogue }
240− </React.Fragment>
421+ </React.Fragment>
244export default StatelessTransaction
426export default StatelessTransaction