@@ -7,238 +7,381 @@ import { CheckboxButton, Input, InnerLabel } from './Base'
8import { renderSignedTx, signTransaction } from '../lib/txn'
8import { renderSignedTx, signTransaction } from '../lib/txn'
10−const StatelessTransaction = props => {
10+class StatelessTransaction extends React.Component {
11− const { web3, gasPrice, gasLimit, nonce, chainId } = props
11+
12− const { setNonce, setChainId, setGasLimit, setGasPrice, showGasDetails, toggleGasDetails } = props
12+ constructor(props) {
18+ showGasDetails: false,
27+ this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
28+ this.handleSubmit = this.handleSubmit.bind(this)
29+ this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
30+ this.handleSetTxn = this.handleSetTxn.bind(this)
31+ this.handleSetStx = this.handleSetStx.bind(this)
32+ this.handleSetNonce = this.handleSetNonce.bind(this)
33+ this.handleSetChainId = this.handleSetChainId.bind(this)
34+ this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
35+ this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
36+ this.toggleGasDetails = this.toggleGasDetails.bind(this)
39+ componentDidMount() {
40+ const { props } = this
42+ const addr = props.wallet.matchWith({
43+ Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
45+ throw BRIDGE_ERROR.MISSING_WALLET
49+ props.web3.matchWith({
52+ const validWeb3 = w3.value
54+ const getTxMetadata = [
55+ validWeb3.eth.getTransactionCount(addr),
56+ validWeb3.eth.net.getId(),
57+ validWeb3.eth.getGasPrice()
60+ Promise.all(getTxMetadata).then(r => {
64+ gasPrice: fromWei(r[2], 'gwei'),
67+ this.setState({...txMetadata})
75+ const { props, state } = this
76+ sendSignedTransaction(props.web3.value, state.stx)
78+ props.setTxnHashCursor(sent)
80+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
83+ this.setState({ txError: err.map(val => val.merge()) })
87+ handleSetUserApproval(){
89+ this.setState({ userApproval: !state.userApproval })
94+ showGasDetails: !this.state.showGasDetails
101+ userApproval: false,
106+ this.setState({ txn })
109+ handleCreateUnsignedTxn() {
13− const { txn, stx, createUnsignedTxn } = props
110+ const txn = this.props.createUnsignedTxn()
14− const { canSign, canGenerate, canApprove } = props
111+ this.setState({ txn })
15− const { setUserApproval, userApproval } = props
112+ }
16− const { canSend, handleSubmit } = props
113+
116+ userApproval: false,
117+ txn: Maybe.Nothing(),
118+ stx: Maybe.Nothing(),
122+ handleSetNonce(nonce){
123+ this.setState({ nonce })
124+ this.handleClearStx()
127+ handleSetChainId(chainId){
128+ this.setState({ chainId })
129+ this.handleClearStx()
132+ handleSetGasPrice(gasPrice){
133+ this.setState({ gasPrice })
134+ this.handleClearStx()
137+ handleRangeChange(e) => {
138+ this.handleSetGasPrice(e.target.value);
141+ handleSetGasLimit(gasLimit){
142+ this.setState({ gasLimit })
143+ this.handleClearStx()
148+ userApproval: false,
149+ stx: Maybe.Nothing(),
18 // TODO: Investigate
153 // TODO: Investigate
19− // setState doesn't seem to work in SetProxy/handleSubmit;
154+ // setState doesn't seem to work in SetProxy/handleSubmit;
20 // - TypeError: Cannot read property 'updater' of undefined
155 // - TypeError: Cannot read property 'updater' of undefined
21 // so just modifying the DOM manually here. (https://imgur.com/a/i0Qsyq1)
156 // 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) => {
158+ sendTxn(e) {
28 e.target.setAttribute("disabled", true);
159 e.target.setAttribute("disabled", true);
29 let spinner = e.target.querySelectorAll('.btn-spinner')[0];
160 let spinner = e.target.querySelectorAll('.btn-spinner')[0];
30 spinner.classList.remove('hide');
161 spinner.classList.remove('hide');
31− handleSubmit()
162+ this.handleSubmit()
34− const generateButtonColor =
35− Nothing.hasInstance(txn)
39− const signerButtonColor =
40− Nothing.hasInstance(stx)
44− const generateTxnButton =
47− disabled={ !canGenerate }
48− prop-color={ generateButtonColor }
49− prop-size={ 'lg wide' }
50− onClick={ createUnsignedTxn }
52− { 'Generate Transaction' }
55− const unsignedTxnDisplay = txn.matchWith({
59− <H3 className={ 'mt-8' }>
60− { 'Unsigned Transaction' }
63− { JSON.stringify(tx.value, null, 2) }
166+ const { web3, createUnsignedTxn, canGenerate } = this.props
167+ const { gasPrice, gasLimit, nonce, chainId, txn, stx, userApproval } = this.state
169+ const { setNonce, setChainId, setGasLimit, setGasPrice, handleSubmit } = this
170+ const { showGasDetails, toggleGasDetails, setUserApproval } = this
172+ const canSign = Maybe.Just.hasInstance(state.txn)
173+ const canApprove = Maybe.Just.hasInstance(state.stx)
174+ const canSend = Maybe.Just.hasInstance(state.stx) && state.userApproval === true
176+ const generateButtonColor =
177+ Nothing.hasInstance(txn)
181+ const signerButtonColor =
182+ Nothing.hasInstance(stx)
186+ const generateTxnButton =
188+ className={ 'mt-8' }
189+ disabled={ !canGenerate }
190+ prop-color={ generateButtonColor }
191+ prop-size={ 'lg wide' }
192+ onClick={ createUnsignedTxn }
194+ { 'Generate Transaction' }
197+ const unsignedTxnDisplay = txn.matchWith({
201+ <H3 className={ 'mt-8' }>
202+ { 'Unsigned Transaction' }
205+ { JSON.stringify(tx.value, null, 2) }
68− const gasPriceRangeDialogue = (
210+ const gasPriceRangeDialogue = (
69− <React.Fragment>
211+ <React.Fragment>
70− <div className="mt-12 flex space-between align-baseline">
212+ <div className="mt-12 flex space-between align-baseline">
72− <span>Gas Price:</span>
214+ <span>Gas Price:</span>
73− <span className="ml-4 text-700 text-sm">{gasPrice} gwei</span>
215+ <span className="ml-4 text-700 text-sm">{gasPrice} gwei</span>
217+ <div className="text-sm">
218+ <span>Max transaction fee: </span>
219+ <span className="text-700">{(gasPrice * gasLimit) / 1000000000} eth</span>
75− <div className="text-sm">
224+ className="mt-4"
76− <span>Max transaction fee: </span>
225+ type="range"
77− <span className="text-700">{(gasPrice * gasLimit) / 1000000000} eth</span>
226+ min="2"
230+ onChange={handleRangeChange}
233+ <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({
159 </React.Fragment>
237 </React.Fragment>
162− const signTxnButton =
240+ const toggleGasDetailsDialogue = (
163− <Button
241+ <a href="javascript:void(0)" onClick={toggleGasDetails}>Gas Details</a>
164− disabled={ !canSign }
242+ )
244+ const gasPriceDialogue =
165− className={ 'mt-8' }
246+ className={ 'mono mt-4' }
166− prop-size={ 'lg wide' }
247+ prop-size={ 'md' }
167− prop-color={ signerButtonColor }
248+ prop-format={ 'innerLabel' }
168− onClick={ () => signTransaction(props) }
249+ value={ gasPrice }
169− >
250+ onChange={ setGasPrice }
170− { 'Sign Transaction' }
251+ >
171− </Button>
252+ <InnerLabel>
172−
253+ { 'Gas Price (gwei)' }
173− const signedTxnDisplay = stx.matchWith({
254+ </InnerLabel>
174− Nothing: _ => '',
255+ </Input>
176− <React.Fragment>
257+ const gasLimitDialogue =
177− <H3 className={ 'mt-8' }>
259+ className={ 'mono mt-4' }
178− { 'Signed Transaction' }
260+ prop-size={ 'md' }
179− </H3>
261+ prop-format={ 'innerLabel' }
180− <Code>
262+ value={ gasLimit }
181− { JSON.stringify(renderSignedTx(tx.value), null, 2) }
263+ onChange={ setGasLimit }>
182− </Code>
264+ <InnerLabel>
183− </React.Fragment>
265+ { 'Gas Limit' }
184− })
266+ </InnerLabel>
186− const confirmButton =
268+
187− <CheckboxButton
269+ const nonceDialogue =
188− className={ 'mt-8' }
271+ className={ 'mono mt-4' }
189− disabled={ !canApprove }
272+ prop-size={ 'md' }
190− onClick={ setUserApproval }
273+ prop-format={ 'innerLabel' }
191− state={ userApproval }
274+ value={ nonce }
275+ onChange={ setNonce }
193− <div>
277+ <InnerLabel>
194− { `I approve this transaction and wish to send.` }
278+ { 'Nonce' }
195− </div>
279+ </InnerLabel>
196− </CheckboxButton>
280+ </Input>
198− const sendTxnButton =
282+ const chainDialogue =
284+ className={ 'mono mt-4 mb-8' }
200− prop-size={ 'xl wide' }
285+ prop-size={ 'md' }
201− className={ 'mt-8' }
286+ prop-format={ 'innerLabel' }
202− disabled={ !canSend }
287+ value={ chainId }
203− onClick={ sendTxn }
288+ onChange={ setChainId }
205− <span className="relative">
290+ <InnerLabel>
206− <span className="btn-spinner hide"></span>
291+ { 'Chain ID' }
207− { 'Send Transaction' }
292+ </InnerLabel>
208− </span>
293+ </Input>
211− const sendDialogue = web3.matchWith({
295+ const onlineParamsDialogue = web3.matchWith({
212− Nothing: _ => '',
296+ Just: _ => <div />,
213− Just: _ =>
297+ Nothing: _ =>
214− <React.Fragment>
298+ <React.Fragment>
215− { confirmButton }
299+ { nonceDialogue }
216− { sendTxnButton }
300+ { chainDialogue }
217− </React.Fragment>
301+ </React.Fragment>
220− return (
304+ const signTxnButton =
221− <React.Fragment>
305+ <Button
222− { generateTxnButton }
306+ disabled={ !canSign }
223− { unsignedTxnDisplay }
307+ className={ 'mt-8' }
308+ prop-size={ 'lg wide' }
309+ prop-color={ signerButtonColor }
310+ onClick={ () => signTransaction(this.props) }
312+ { 'Sign Transaction' }
225− { gasPriceRangeDialogue }
315+ const signedTxnDisplay = stx.matchWith({
226− { toggleGasDetailsDialogue }
316+ Nothing: _ => '',
319+ <H3 className={ 'mt-8' }>
320+ { 'Signed Transaction' }
323+ { JSON.stringify(renderSignedTx(tx.value), null, 2) }
228− { showGasDetails &&
328+ const confirmButton =
330+ className={ 'mt-8' }
331+ disabled={ !canApprove }
332+ onClick={ setUserApproval }
333+ state={ userApproval }
230− { gasPriceDialogue }
336+ { `I approve this transaction and wish to send.` }
231− { gasLimitDialogue }
233− }
338+ </CheckboxButton>
234− { onlineParamsDialogue }
339+
340+ const sendTxnButton =
342+ prop-size={ 'xl wide' }
343+ className={ 'mt-8' }
344+ disabled={ !canSend }
347+ <span className="relative">
348+ <span className="btn-spinner hide"></span>
349+ { 'Send Transaction' }
353+ const sendDialogue = web3.matchWith({
364+ { generateTxnButton }
365+ { unsignedTxnDisplay }
236− { signTxnButton }
367+ { gasPriceRangeDialogue }
368+ { toggleGasDetailsDialogue }
372+ { gasPriceDialogue }
373+ { gasLimitDialogue }
376+ { onlineParamsDialogue }
238− { signedTxnDisplay }
380+ { signedTxnDisplay }
239− { sendDialogue }
381+ { sendDialogue }
240− </React.Fragment>
382+ </React.Fragment>
244export default StatelessTransaction
387export default StatelessTransaction