OSDN Git Service

update the build-chain-transactions function.
[bytom/bytom-electron.git] / src / features / transactions / components / New / IssueAssets.jsx
1 import {
2   BaseNew,
3   FormSection,
4   Autocomplete,
5   ObjectSelectorField,
6   TextField,
7   AmountField,
8   PasswordField,
9   RadioField,
10   KeyValueTable,
11   GasField,
12 } from 'features/shared/components'
13 import { Connection } from 'sdk'
14 import {chainClient} from 'utility/environment'
15 import { addZeroToDecimalPosition } from 'utility/buildInOutDisplay'
16 import  TxContainer  from './NewTransactionsContainer/TxContainer'
17 import {reduxForm} from 'redux-form'
18 import React from 'react'
19 import styles from './New.scss'
20 import actions from 'actions'
21 import { btmID } from 'utility/environment'
22 import {getAssetDecimal, issueAssetTxActionBuilder} from '../../transactions'
23 import {withNamespaces} from 'react-i18next'
24
25
26 class IssueAssets extends React.Component {
27   constructor(props) {
28     super(props)
29     this.connection = chainClient().connection
30     this.state = {
31       estimateGas:null,
32       counter: 1,
33     }
34
35     this.submitWithValidation = this.submitWithValidation.bind(this)
36     this.addReceiverItem = this.addReceiverItem.bind(this)
37     this.removeReceiverItem = this.removeReceiverItem.bind(this)
38   }
39
40   submitWithValidation(data) {
41     return new Promise((resolve, reject) => {
42       this.props.submitForm(Object.assign({}, data, {state: this.state, form: 'issueAssetTx'}))
43         .catch((err) => {
44           const response = {}
45
46           if (err.data) {
47             response.actions = []
48           }
49
50           response['_error'] = err
51           return reject(response)
52         })
53     })
54   }
55
56   addReceiverItem() {
57     const counter = this.state.counter
58     this.props.fields.receivers.addField({
59       id: counter
60     })
61     this.setState({
62       counter: counter+1,
63     })
64   }
65
66   removeReceiverItem(index) {
67     this.props.fields.receivers.removeField(index)
68   }
69
70   decodeRawTx(e){
71     try {
72       const rawTransaction = Connection.camelize(JSON.parse(e.target.value)).rawTransaction
73       this.props.decode(rawTransaction)
74     } catch (e) {
75     }
76   }
77
78   componentWillReceiveProps(nextProps) {
79     if(nextProps.decodedTx.length !== 0 && nextProps.decodedTx !== this.props.decodedTx && nextProps.fields.submitAction.value === 'sign'){
80       const transaction = nextProps.decodedTx
81
82       const inputs = transaction.inputs
83       const outputs = transaction.outputs
84
85       const issueAction = inputs.filter(input => input.type === 'issue')[0]
86       const issueAssetId = issueAction.assetId
87
88       const issueReceivers = outputs.filter(output => output.assetId == issueAssetId)
89
90       const diffLength = issueReceivers.length - this.props.fields.receivers.length
91
92       if(diffLength > 0 ){
93         const counter = this.state.counter
94         for (let i = 0; i < diffLength; i++) {
95           this.props.fields.receivers.addField({
96             id: counter+i
97           })
98         }
99         this.setState({
100           counter: counter+diffLength,
101         })
102       }else if(diffLength < 0){
103         for (let i = 0; i < -diffLength; i++) {
104           this.removeReceiverItem(i)
105         }
106       }
107     }
108
109     else if( nextProps.fields.submitAction.value === 'submit' && this.props.fields.submitAction.value === 'sign'){
110       const length = nextProps.fields.receivers.length
111       if(length>1){
112         for (let i = 0; i < (length-1) ; i++) {
113           nextProps.fields.receivers.removeField(i)
114         }
115       }
116     }
117
118   }
119
120   estimateNormalTransactionGas() {
121     const transaction = this.props.fields
122     const accountAlias = transaction.accountAlias.value
123     const accountId = transaction.accountId.value
124     const assetAlias = transaction.assetAlias.value
125     const assetId = transaction.assetId.value
126     const receivers = transaction.receivers
127     const addresses = receivers.map(x => x.address.value)
128     const amounts = receivers.map(x => Number(x.amount.value))
129
130     const {t, i18n} = this.props
131
132     const noAccount = !accountAlias && !accountId
133     const noAsset = !assetAlias && !assetId
134
135     if ( addresses.includes('') || amounts.includes(0)|| noAccount || noAsset) {
136       this.setState({estimateGas: null})
137       return
138     }
139
140     const actions = issueAssetTxActionBuilder(transaction, Math.pow(10, 7), 'amount.value' )
141
142     const body = {actions, ttl: 1}
143     this.connection.request('/build-transaction', body).then(resp => {
144       if (resp.status === 'fail') {
145         this.setState({estimateGas: null})
146         const errorMsg =  resp.code && i18n.exists(`btmError.${resp.code}`) && t(`btmError.${resp.code}`) || resp.msg
147         this.props.showError(new Error(errorMsg))
148         return
149       }
150
151       return this.connection.request('/estimate-transaction-gas', {
152         transactionTemplate: resp.data
153       }).then(resp => {
154         if (resp.status === 'fail') {
155           this.setState({estimateGas: null})
156           const errorMsg =  resp.code && i18n.exists(`btmError.${resp.code}`) && t(`btmError.${resp.code}`) || resp.msg
157           this.props.showError(new Error(errorMsg))
158           return
159         }
160         this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
161       })
162     })
163   }
164
165   render() {
166     const {
167       fields: {assetAlias, assetId, receivers, password, submitAction, signTransaction, accountId, accountAlias, gasLevel},
168       error,
169       handleSubmit,
170       submitting
171     } = this.props
172     const t = this.props.t;
173     [accountAlias, accountId, assetAlias, assetId].forEach(key => {
174       key.onBlur = this.estimateNormalTransactionGas.bind(this)
175     });
176     (receivers.map(receiver => receiver.amount)).forEach(amount =>{
177       amount.onBlur = this.estimateNormalTransactionGas.bind(this)
178     })
179
180     let submitLabel = t('transaction.new.submit')
181     if (submitAction.value == 'sign') {
182       submitLabel = t('transaction.issue.signTx')
183     }
184
185     let gas
186     const options = [
187       {label: t('transaction.advance.submitToBlockchain') , value: 'submit'},
188       {label: t('transaction.issue.signRaw'), value: 'sign'}
189     ]
190
191     const showBtmAmountUnit = (assetAlias.value === 'BTM' || assetId.value === btmID)
192     const assetDecimal = getAssetDecimal(this.props.fields, this.props.asset) || 0
193
194     const asset = this.props.asset.filter(a => (a.id === assetId.value || a.alias === assetAlias.value))[0]
195
196     let assetItem
197
198     if (submitAction.value === 'sign' && this.props.decodedTx.length !== 0 && signTransaction.value && signTransaction.valid) {
199       const transaction = this.props.decodedTx
200
201       const inputs = transaction.inputs
202       const outputs = transaction.outputs
203
204       const issueAction = inputs.filter(input => input.type === 'issue')[0]
205       const issueAssetId = issueAction.assetId
206       assetId.value = issueAssetId
207
208       gas = transaction.fee / Math.pow(10, 8) + ' BTM'
209
210       accountAlias.value = inputs.filter(input => input.type === 'spend')[0].address
211
212       const assetDefinition = issueAction.assetDefinition
213
214       assetItem = <KeyValueTable
215         title={t('form.assetDefinition')}
216         id={issueAssetId}
217         object='asset'
218         items={[
219           {label: 'ID', value: issueAssetId},
220           {label: t('form.alias'), value: assetDefinition.name},
221           {label: t('form.symbol'), value: assetDefinition.symbol},
222           {label: t('form.decimals'), value: assetDefinition.decimals},
223           {label: t('form.reissueTitle'), value: assetDefinition.reissue || 'true'},
224           {label: t('form.quorum'), value: assetDefinition.quorum},
225           {label: t('asset.additionInfo'), value: assetDefinition.description},
226         ]}
227       />
228
229       const issueReceivers = outputs.filter(output => output.assetId == issueAssetId)
230
231       receivers.map((receiver, index) =>{
232         if(issueReceivers[index]){
233           receiver.address.value = issueReceivers[index].address
234           receiver.amount.value = addZeroToDecimalPosition((issueReceivers[index].amount/Math.pow(10, assetDefinition.decimals)), Number(assetDefinition.decimals))
235         }
236       })
237
238     } else if (asset) {
239       assetItem = <KeyValueTable
240         title={t('form.assetDefinition')}
241         id={asset.id}
242         object='asset'
243         items={[
244           {label: 'ID', value: asset.id},
245           {label: t('form.alias'), value: asset.alias},
246           {label: t('form.symbol'), value: asset.definition.symbol},
247           {label: t('form.decimals'), value: asset.definition.decimals},
248           {label: t('form.reissueTitle'), value: (asset.alias === 'BTM' || asset.limitHeight > 0) ? 'false' : 'true'},
249           {label: t('form.xpubs'), value: (asset.xpubs || []).length},
250           {label: t('form.quorum'), value: asset.quorum},
251           {label: t('asset.additionInfo'), value: asset.definition.description},
252         ]}
253       />
254     }
255
256     return (
257       <TxContainer
258         error={error}
259         onSubmit={handleSubmit(this.submitWithValidation)}
260         submitting={submitting}
261         submitLabel= {submitLabel}
262         className={styles.container}
263       >
264
265         <FormSection  title={t('transaction.issue.issueAsset')}>
266           {assetItem}
267           <label className={styles.title}>{t('form.input')}</label>
268           <div className={styles.mainBox}>
269             {
270               submitAction.value === 'sign'?
271                 <TextField title={t('transaction.issue.accountAddress')}
272                            disabled = {true}
273                            fieldProps={accountAlias}/>
274                 :
275                 [
276                   <ObjectSelectorField
277                     key='asset-selector-field'
278                     keyIndex='normaltx-asset'
279                     title={ t('transaction.issue.issueAsset')}
280                     aliasField={Autocomplete.AssetAlias}
281                     fieldProps={{
282                       id: assetId,
283                       alias: assetAlias
284                     }}
285                   />,
286                   <ObjectSelectorField
287                     key='account-selector-field'
288                     keyIndex='normaltx-account'
289                     title={t('transaction.issue.gasAccount')}
290                     aliasField={Autocomplete.AccountAlias}
291                     fieldProps={{
292                       id: accountId,
293                       alias: accountAlias
294                     }}
295                   />
296                 ]
297             }
298           </div>
299           <label className={styles.title}>{t('form.output')}</label>
300           <div className={styles.mainBox}>
301             {receivers.map((receiver, index) =>
302               <div
303                 className={this.props.tutorialVisible? styles.tutorialItem: styles.subjectField}
304                 key={`issueAsset-${receiver.id.value}`}>
305                 <TextField title={t('form.address')}
306                            disabled = {submitAction.value === 'sign'}
307                            fieldProps={{
308                              ...receiver.address,
309                              onBlur: (e) => {
310                                receiver.address.onBlur(e)
311                              },
312                            }}/>
313
314                 {
315                   submitAction.value === 'sign'?
316                     <TextField
317                       title={t('form.amount')}
318                       disabled = {true}
319                       fieldProps={receiver.amount}
320                     />:
321                     <AmountField
322                       isBTM={showBtmAmountUnit}
323                       title={t('form.amount')}
324                       fieldProps={receiver.amount}
325                       decimal={assetDecimal}
326                     />
327                 }
328
329                 <button
330                   className={`btn btn-danger btn-xs ${styles.deleteButton}`}
331                   tabIndex='-1'
332                   type='button'
333                   disabled = {submitAction.value === 'sign'}
334                   onClick={() => this.removeReceiverItem(index)}
335                 >
336                   {t('commonWords.remove')}
337                 </button>
338               </div>
339             )}
340             <button
341               type='button'
342               className='btn btn-default'
343               disabled = {submitAction.value === 'sign'}
344               onClick={this.addReceiverItem}
345             >
346               {t('commonWords.addField')}
347             </button>
348           </div>
349         </FormSection>
350         <FormSection  title= { 'Gas'}>
351
352             {
353               submitAction.value === 'sign'?
354                 gas || ''
355                 :
356                 <div className={styles.txFeeBox}>
357                   <GasField
358                     gas={this.state.estimateGas}
359                     fieldProps={gasLevel}
360                     btmAmountUnit={this.props.btmAmountUnit}
361                   />
362                   <span className={styles.feeDescription}> {t('transaction.normal.feeDescription')}</span>
363                 </div>
364             }
365
366         </FormSection>
367
368         <FormSection  title= { t('transaction.issue.transactionType')}>
369           <RadioField title={t('transaction.advance.buildType')} options={options} fieldProps={submitAction} />
370           {
371             submitAction.value === 'sign' &&
372             <TextField
373               title={t('transaction.advance.toSignTransaction')}
374               fieldProps={{
375                 ...signTransaction,
376                 onBlur: (e) => {
377                   signTransaction.onBlur(e)
378                   this.decodeRawTx(e)
379                 },
380               }}
381             />
382           }
383         </FormSection>
384
385         <FormSection  title={ t('key.password') }>
386           <PasswordField
387             placeholder={t('key.passwordPlaceholder')}
388             fieldProps={password}
389           />
390         </FormSection>
391       </TxContainer>
392
393     )
394   }
395 }
396
397 const validate = (values, props) => {
398   const errors = {}
399   const t = props.t
400
401   // Base transaction
402   let baseTx = (values.signTransaction || '').trim()
403   try {
404     JSON.parse(baseTx)
405   } catch (e) {
406     if (baseTx && e) {
407       errors.signTransaction = t('errorMessage.jsonError')
408     }
409   }
410
411   return errors
412 }
413
414 const asyncValidate = (values, dispatch, props) => {
415   const errors = []
416   const promises = []
417
418   values.receivers.forEach((receiver, idx) => {
419     const address = values.receivers[idx].address
420     if ( !address || address.length === 0)
421       promises.push(Promise.resolve())
422     else{
423       promises.push(
424         chainClient().accounts.validateAddresses(address)
425           .then(
426             (resp) => {
427               if (!resp.data.valid) {
428                 errors[idx] = {address: props.t('errorMessage.addressError')}
429               }
430               return {}
431             }
432           ))
433     }
434   })
435
436   return Promise.all(promises).then(() => {
437     if (errors.length > 0) throw {
438       receivers: errors
439     }
440     return {}
441   })
442 }
443
444 const mapDispatchToProps = (dispatch) => ({
445   ...BaseNew.mapDispatchToProps('transaction')(dispatch),
446   decode: (transaction) => dispatch( actions.transaction.decode(transaction)),
447   showError: err => dispatch({type: 'ERROR', payload: err}),
448 })
449
450 const mapStateToProps = (state, ownProps) => ({
451   ...BaseNew.mapStateToProps('transaction')(state, ownProps),
452   decodedTx: state.transaction.decodedTx,
453   initialValues:{
454     assetAlias: ownProps.location.query.alias,
455     assetId:'',
456     submitAction: 'submit',
457     gasLevel: '1',
458     receivers:[{
459       id: 0,
460       amount:'',
461       address:''
462     }]
463   }
464 })
465
466 export default withNamespaces('translations') (BaseNew.connect(
467   mapStateToProps,
468   mapDispatchToProps,
469   reduxForm({
470     form: 'IssueAssetTxForm',
471     fields: [
472       'assetAlias',
473       'assetId',
474       'receivers[].id',
475       'receivers[].amount',
476       'receivers[].address',
477       'password',
478       'submitAction',
479       'signTransaction',
480       'accountAlias',
481       'accountId',
482       'gasLevel'
483     ],
484     asyncValidate,
485     asyncBlurFields: ['receivers[].address'],
486     validate,
487     touchOnChange: true,
488   }
489   )(IssueAssets)
490 ))