on master help sign in
- WIP move transaction state from setProxy to statelessTransaction
4f34a5fChris Johnson
src/bridge/components/StatelessTransaction.js+356−213
@@ -7,238 +7,381 @@ import { CheckboxButton, Input, InnerLabel } from './Base'
7
7
8import { renderSignedTx, signTransaction } from '../lib/txn'
8import { renderSignedTx, signTransaction } from '../lib/txn'
9
9
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) {
13+ super(props)
14+
15+ this.state = {
16+ gasPrice: '5',
17+ gasLimit: '600000',
18+ showGasDetails: false,
19+ userApproval: false,
20+ chainId: '',
21+ nonce: '',
22+ stx: Nothing(),
23+ txn: Nothing(),
24+ txError: Nothing(),
25+ }
26+
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)
37+ }
38+
39+ componentDidMount() {
40+ const { props } = this
41+
42+ const addr = props.wallet.matchWith({
43+ Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
44+ Nothing: () => {
45+ throw BRIDGE_ERROR.MISSING_WALLET
46+ }
47+ })
48+
49+ props.web3.matchWith({
50+ Nothing: () => {},
51+ Just: (w3) => {
52+ const validWeb3 = w3.value
53+
54+ const getTxMetadata = [
55+ validWeb3.eth.getTransactionCount(addr),
56+ validWeb3.eth.net.getId(),
57+ validWeb3.eth.getGasPrice()
58+ ];
59+
60+ Promise.all(getTxMetadata).then(r => {
61+ const txMetadata = {
62+ nonce: r[0],
63+ chainId: r[1],
64+ gasPrice: fromWei(r[2], 'gwei'),
65+ };
66+
67+ this.setState({...txMetadata})
68+
69+ })
70+ }
71+ });
72+ }
73+
74+ handleSubmit(){
75+ const { props, state } = this
76+ sendSignedTransaction(props.web3.value, state.stx)
77+ .then(sent => {
78+ props.setTxnHashCursor(sent)
79+ props.popRoute()
80+ props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
81+ })
82+ .catch(err => {
83+ this.setState({ txError: err.map(val => val.merge()) })
84+ })
85+ }
86+
87+ handleSetUserApproval(){
88+ const {state} = this
89+ this.setState({ userApproval: !state.userApproval })
90+ }
91+
92+ toggleGasDetails() {
93+ this.setState({
94+ showGasDetails: !this.state.showGasDetails
95+ })
96+ }
97+
98+ handleSetStx(stx){
99+ this.setState({
100+ stx,
101+ userApproval: false,
102+ })
103+ }
104+
105+ handleSetTxn(txn){
106+ this.setState({ txn })
107+ }
108+
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+
114+ handleClearTxn() {
115+ this.setState({
116+ userApproval: false,
117+ txn: Maybe.Nothing(),
118+ stx: Maybe.Nothing(),
119+ })
120+ }
121+
122+ handleSetNonce(nonce){
123+ this.setState({ nonce })
124+ this.handleClearStx()
125+ }
126+
127+ handleSetChainId(chainId){
128+ this.setState({ chainId })
129+ this.handleClearStx()
130+ }
131+
132+ handleSetGasPrice(gasPrice){
133+ this.setState({ gasPrice })
134+ this.handleClearStx()
135+ }
136+
137+ handleRangeChange(e) => {
138+ this.handleSetGasPrice(e.target.value);
139+ }
140+
141+ handleSetGasLimit(gasLimit){
142+ this.setState({ gasLimit })
143+ this.handleClearStx()
144+ }
145+
146+ handleClearStx() {
147+ this.setState({
148+ userApproval: false,
149+ stx: Maybe.Nothing(),
150+ })
151+ }
17
152
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)
22
157
23− const handleRangeChange = (e) => {
24− setGasPrice(e.target.value);
25− }
26−
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()
32− }
163+ }
33−
34− const generateButtonColor =
35− Nothing.hasInstance(txn)
36− ? 'blue'
37− : 'green'
38−
39− const signerButtonColor =
40− Nothing.hasInstance(stx)
41− ? 'blue'
42− : 'green'
43−
44− const generateTxnButton =
45− <Button
46− className={ 'mt-8' }
47− disabled={ !canGenerate }
48− prop-color={ generateButtonColor }
49− prop-size={ 'lg wide' }
50− onClick={ createUnsignedTxn }
51− >
52− { 'Generate Transaction' }
53− </Button>
54−
55− const unsignedTxnDisplay = txn.matchWith({
56− Nothing: _ => '',
57− Just: tx =>
58− <React.Fragment>
59− <H3 className={ 'mt-8' }>
60− { 'Unsigned Transaction' }
61− </H3>
62− <Code>
63− { JSON.stringify(tx.value, null, 2) }
64− </Code>
65− </React.Fragment>
66− })
67
164
165+ render() {
166+ const { web3, createUnsignedTxn, canGenerate } = this.props
167+ const { gasPrice, gasLimit, nonce, chainId, txn, stx, userApproval } = this.state
168+
169+ const { setNonce, setChainId, setGasLimit, setGasPrice, handleSubmit } = this
170+ const { showGasDetails, toggleGasDetails, setUserApproval } = this
171+
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
175+
176+ const generateButtonColor =
177+ Nothing.hasInstance(txn)
178+ ? 'blue'
179+ : 'green'
180+
181+ const signerButtonColor =
182+ Nothing.hasInstance(stx)
183+ ? 'blue'
184+ : 'green'
185+
186+ const generateTxnButton =
187+ <Button
188+ className={ 'mt-8' }
189+ disabled={ !canGenerate }
190+ prop-color={ generateButtonColor }
191+ prop-size={ 'lg wide' }
192+ onClick={ createUnsignedTxn }
193+ >
194+ { 'Generate Transaction' }
195+ </Button>
196+
197+ const unsignedTxnDisplay = txn.matchWith({
198+ Nothing: _ => '',
199+ Just: tx =>
200+ <React.Fragment>
201+ <H3 className={ 'mt-8' }>
202+ { 'Unsigned Transaction' }
203+ </H3>
204+ <Code>
205+ { JSON.stringify(tx.value, null, 2) }
206+ </Code>
207+ </React.Fragment>
208+ })
209+
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">
71− <div>
213+ <div>
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>
216+ </div>
217+ <div className="text-sm">
218+ <span>Max transaction fee: </span>
219+ <span className="text-700">{(gasPrice * gasLimit) / 1000000000} eth</span>
220+ </div>
74 </div>
221 </div>
222+
223+ <input
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"
227+ max="20"
228+ list="gweiVals"
229+ value={gasPrice}
230+ onChange={handleRangeChange}
231+ />
232+
233+ <div className="flex space-between text-sm mb-8">
234+ <div>Cheap</div>
235+ <div>Fast</div>
78 </div>
236 </div>
79− </div>
80−
81− <input
82− className="mt-4"
83− type="range"
84− min="2"
85− max="20"
86− list="gweiVals"
87− value={gasPrice}
88− onChange={handleRangeChange}
89− />
90−
91− <div className="flex space-between text-sm mb-8">
92− <div>Cheap</div>
93− <div>Fast</div>
94− </div>
95− </React.Fragment>
96− )
97−
98− const toggleGasDetailsDialogue = (
99− <a href="javascript:void(0)" onClick={toggleGasDetails}>Gas Details</a>
100− )
101−
102− const gasPriceDialogue =
103− <Input
104− className={ 'mono mt-4' }
105− prop-size={ 'md' }
106− prop-format={ 'innerLabel' }
107− value={ gasPrice }
108− onChange={ setGasPrice }
109− >
110− <InnerLabel>
111− { 'Gas Price (gwei)' }
112− </InnerLabel>
113− </Input>
114−
115− const gasLimitDialogue =
116− <Input
117− className={ 'mono mt-4' }
118− prop-size={ 'md' }
119− prop-format={ 'innerLabel' }
120− value={ gasLimit }
121− onChange={ setGasLimit }>
122− <InnerLabel>
123− { 'Gas Limit' }
124− </InnerLabel>
125− </Input>
126−
127− const nonceDialogue =
128− <Input
129− className={ 'mono mt-4' }
130− prop-size={ 'md' }
131− prop-format={ 'innerLabel' }
132− value={ nonce }
133− onChange={ setNonce }
134− >
135− <InnerLabel>
136− { 'Nonce' }
137− </InnerLabel>
138− </Input>
139−
140− const chainDialogue =
141− <Input
142− className={ 'mono mt-4 mb-8' }
143− prop-size={ 'md' }
144− prop-format={ 'innerLabel' }
145− value={ chainId }
146− onChange={ setChainId }
147− >
148− <InnerLabel>
149− { 'Chain ID' }
150− </InnerLabel>
151− </Input>
152−
153− const onlineParamsDialogue = web3.matchWith({
154− Just: _ => <div />,
155− Nothing: _ =>
156− <React.Fragment>
157− { nonceDialogue }
158− { chainDialogue }
159 </React.Fragment>
237 </React.Fragment>
160− })
238+ )
161−
239+
162− const signTxnButton =
240+ const toggleGasDetailsDialogue = (
163− <Button
241+ <a href="javascript:void(0)" onClick={toggleGasDetails}>Gas Details</a>
164− disabled={ !canSign }
242+ )
243+
244+ const gasPriceDialogue =
245+ <Input
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>
175− Just: tx =>
256+
176− <React.Fragment>
257+ const gasLimitDialogue =
258+ <Input
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>
185−
267+ </Input>
186− const confirmButton =
268+
187− <CheckboxButton
269+ const nonceDialogue =
270+ <Input
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 }
192− >
276+ >
193− <div>
277+ <InnerLabel>
194− { `I approve this transaction and wish to send.` }
278+ { 'Nonce' }
195− </div>
279+ </InnerLabel>
196− </CheckboxButton>
280+ </Input>
197−
281+
198− const sendTxnButton =
282+ const chainDialogue =
199− <Button
283+ <Input
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 }
204− >
289+ >
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>
209− </Button>
294+
210−
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>
218− })
302+ })
219
303
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) }
311+ >
312+ { 'Sign Transaction' }
313+ </Button>
224
314
225− { gasPriceRangeDialogue }
315+ const signedTxnDisplay = stx.matchWith({
226− { toggleGasDetailsDialogue }
316+ Nothing: _ => '',
317+ Just: tx =>
318+ <React.Fragment>
319+ <H3 className={ 'mt-8' }>
320+ { 'Signed Transaction' }
321+ </H3>
322+ <Code>
323+ { JSON.stringify(renderSignedTx(tx.value), null, 2) }
324+ </Code>
325+ </React.Fragment>
326+ })
227
327
228− { showGasDetails &&
328+ const confirmButton =
329+ <CheckboxButton
330+ className={ 'mt-8' }
331+ disabled={ !canApprove }
332+ onClick={ setUserApproval }
333+ state={ userApproval }
334+ >
229 <div>
335 <div>
230− { gasPriceDialogue }
336+ { `I approve this transaction and wish to send.` }
231− { gasLimitDialogue }
232 </div>
337 </div>
233− }
338+ </CheckboxButton>
234− { onlineParamsDialogue }
339+
340+ const sendTxnButton =
341+ <Button
342+ prop-size={ 'xl wide' }
343+ className={ 'mt-8' }
344+ disabled={ !canSend }
345+ onClick={ sendTxn }
346+ >
347+ <span className="relative">
348+ <span className="btn-spinner hide"></span>
349+ { 'Send Transaction' }
350+ </span>
351+ </Button>
352+
353+ const sendDialogue = web3.matchWith({
354+ Nothing: _ => '',
355+ Just: _ =>
356+ <React.Fragment>
357+ { confirmButton }
358+ { sendTxnButton }
359+ </React.Fragment>
360+ })
361+
362+ return (
363+ <React.Fragment>
364+ { generateTxnButton }
365+ { unsignedTxnDisplay }
235
366
236− { signTxnButton }
367+ { gasPriceRangeDialogue }
368+ { toggleGasDetailsDialogue }
237
369
370+ { showGasDetails &&
371+ <div>
372+ { gasPriceDialogue }
373+ { gasLimitDialogue }
374+ </div>
375+ }
376+ { onlineParamsDialogue }
377+
378+ { signTxnButton }
379+
238− { signedTxnDisplay }
380+ { signedTxnDisplay }
239− { sendDialogue }
381+ { sendDialogue }
240− </React.Fragment>
382+ </React.Fragment>
241− )
383+ )
384+ }
242}
385}
243
386
244export default StatelessTransaction
387export default StatelessTransaction
src/bridge/views/SetProxy.js−169
@@ -55,181 +55,12 @@ class SetProxy extends React.Component {
55
55
56 this.state = {
56 this.state = {
57 proxyAddress: '',
57 proxyAddress: '',
58− txn: Maybe.Nothing(),
59− txError: Maybe.Nothing(),
60 issuingPoint: issuingPoint,
58 issuingPoint: issuingPoint,
61− showGasDetails: false,
62− userApproval: false,
63− nonce: '',
64− gasPrice: '5',
65− chainId: '',
66− gasLimit: '600000',
67− stx: Maybe.Nothing(),
68 }
59 }
69
60
70 this.handleAddressInput = this.handleAddressInput.bind(this)
61 this.handleAddressInput = this.handleAddressInput.bind(this)
71− // Transaction
72− this.handleCreateUnsignedTxn = this.handleCreateUnsignedTxn.bind(this)
73− this.handleSubmit = this.handleSubmit.bind(this)
74− this.handleSetUserApproval = this.handleSetUserApproval.bind(this)
75− this.handleSetTxn = this.handleSetTxn.bind(this)
76− this.handleSetStx = this.handleSetStx.bind(this)
77− this.handleSetNonce = this.handleSetNonce.bind(this)
78− this.handleSetChainId = this.handleSetChainId.bind(this)
79− this.handleSetGasPrice = this.handleSetGasPrice.bind(this)
80− this.handleSetGasLimit = this.handleSetGasLimit.bind(this)
81− this.toggleGasDetails = this.toggleGasDetails.bind(this)
82 }
62 }
83
63
84− toggleGasDetails() {
85− this.setState({
86− showGasDetails: !this.state.showGasDetails
87− })
88− }
89−
90− componentDidMount() {
91− const { props } = this
92−
93− const addr = props.wallet.matchWith({
94− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
95− Nothing: () => {
96− throw BRIDGE_ERROR.MISSING_WALLET
97− }
98− })
99−
100− props.web3.matchWith({
101− Nothing: () => {},
102− Just: (w3) => {
103− const validWeb3 = w3.value
104−
105− const getTxMetadata = [
106− validWeb3.eth.getTransactionCount(addr),
107− validWeb3.eth.net.getId(),
108− validWeb3.eth.getGasPrice()
109− ];
110−
111− Promise.all(getTxMetadata).then(r => {
112− const txMetadata = {
113− nonce: r[0],
114− chainId: r[1],
115− gasPrice: fromWei(r[2], 'gwei'),
116− };
117−
118− this.setState({...txMetadata})
119−
120− })
121− }
122− });
123−
124− }
125−
126−
127−
128− handleAddressInput(proxyAddress) {
129− this.setState({ proxyAddress })
130− this.handleClearTxn()
131− }
132−
133−
134− handleCreateUnsignedTxn() {
135− const txn = this.createUnsignedTxn()
136− this.setState({ txn })
137− }
138−
139−
140− handleSetUserApproval(){
141− const {state} = this
142− this.setState({ userApproval: !state.userApproval })
143− }
144−
145−
146−
147− handleSetTxn(txn){
148− this.setState({ txn })
149− }
150−
151−
152−
153− handleSetStx(stx){
154− this.setState({
155− stx,
156− userApproval: false,
157− })
158− }
159−
160−
161−
162− handleSetNonce(nonce){
163− this.setState({ nonce })
164− this.handleClearStx()
165− }
166−
167−
168−
169− handleSetChainId(chainId){
170− this.setState({ chainId })
171− this.handleClearStx()
172− }
173−
174−
175−
176− handleSetGasPrice(gasPrice){
177− this.setState({ gasPrice })
178− this.handleClearStx()
179− }
180−
181−
182−
183− handleSetGasLimit(gasLimit){
184− this.setState({ gasLimit })
185− this.handleClearStx()
186− }
187−
188−
189−
190− handleClearStx() {
191− this.setState({
192− userApproval: false,
193− stx: Maybe.Nothing(),
194− })
195− }
196−
197−
198−
199− handleClearTxn() {
200− this.setState({
201− userApproval: false,
202− txn: Maybe.Nothing(),
203− stx: Maybe.Nothing(),
204− })
205− }
206−
207−
208−
209− handleClearTransaction() {
210− this.setState({
211− userApproval: false,
212− txn: Maybe.Nothing(),
213− stx: Maybe.Nothing(),
214− })
215− }
216−
217−
218−
219− handleSubmit(){
220− const { props, state } = this
221− sendSignedTransaction(props.web3.value, state.stx)
222− .then(sent => {
223− props.setTxnHashCursor(sent)
224− props.popRoute()
225− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION)
226− })
227− .catch(err => {
228− this.setState({ txError: err.map(val => val.merge()) })
229− })
230− }
231−
232−
233 createUnsignedTxn(proxyAddress) {
64 createUnsignedTxn(proxyAddress) {
234 const { state, props } = this
65 const { state, props } = this
235
66