OSDN Git Service

add the Vote in create transaction page.
authorZhiting Lin <zlin035@uottawa.ca>
Wed, 5 Jun 2019 02:18:53 +0000 (10:18 +0800)
committerZhiting Lin <zlin035@uottawa.ca>
Wed, 5 Jun 2019 02:18:53 +0000 (10:18 +0800)
src/features/transactions/actions.js
src/features/transactions/components/New/AdvancedTransactionForm.jsx
src/features/transactions/components/New/FormActionItem.jsx
src/features/transactions/components/New/New.jsx
src/features/transactions/components/New/Vote.jsx [new file with mode: 0644]
src/features/transactions/components/New/VoteConfirmModal/VoteConfirmModal.jsx [new file with mode: 0644]
src/features/transactions/components/New/VoteConfirmModal/VoteConfirmModal.scss [new file with mode: 0644]
src/features/transactions/transactions.js
src/locales/en/translation.json
src/locales/zh/translation.json

index 4944543..2796206 100644 (file)
@@ -3,7 +3,7 @@ import {chainClient} from 'utility/environment'
 import {parseNonblankJSON} from 'utility/string'
 import {push} from 'react-router-redux'
 import {baseCreateActions, baseListActions} from 'features/shared/actions'
-import { normalTxActionBuilder, issueAssetTxActionBuilder } from './transactions'
+import { voteTxActionBuilder, normalTxActionBuilder, issueAssetTxActionBuilder } from './transactions'
 
 const type = 'transaction'
 
@@ -24,6 +24,11 @@ function preprocessTransaction(formParams) {
     builder.actions = normalTxActionBuilder(formParams,  Number(gasPrice), 'amount')
   }
 
+  if (formParams.form === 'voteTx') {
+    const gasPrice = formParams.state.estimateGas * Number(formParams.gasLevel)
+    builder.actions = voteTxActionBuilder(formParams,  Number(gasPrice), formParams.state.address)
+  }
+
   if (formParams.form === 'issueAssetTx') {
     const gasPrice = formParams.state.estimateGas * Number(formParams.gasLevel)
     builder.actions = issueAssetTxActionBuilder(formParams, Number(gasPrice), 'amount')
@@ -41,7 +46,8 @@ function preprocessTransaction(formParams) {
   for (let i in builder.actions) {
     let a = builder.actions[i]
 
-    const intFields = ['amount', 'position']
+    delete a.ID
+    const intFields = ['amount', 'position','sourcePos']
     intFields.forEach(key => {
       const value = a[key]
       if (value) {
@@ -65,7 +71,6 @@ function preprocessTransaction(formParams) {
       throw new Error(`Action ${parseInt(i) + 1} receiver should be valid JSON.`)
     }
   }
-
   return builder
 }
 
@@ -92,7 +97,7 @@ form.submitForm = (formParams) => function (dispatch) {
   }
 
   // normal transactions
-  if( formParams.form === 'normalTx'){
+  if( formParams.form === 'normalTx' || formParams.form === 'voteTx'){
 
     const accountId = formParams.accountId
     const accountAlias = formParams.accountAlias
@@ -230,8 +235,48 @@ const decode = (data) => {
   }
 }
 
+const getAddresses =(params) => {
+  return new Promise((resolve, reject) => {
+    const body = Object.assign({from:0, count:1}, params)
+    chainClient().accounts.listAddresses(body)
+      .then((resp) =>{
+        const result = resp.data
+        if(result.length > 0){
+          resolve(result[0].address)
+        }else{
+          chainClient().accounts.createAddress(params)
+            .then((ret) =>{
+              resolve(ret.data.address)
+            }).catch((err) => reject( err))
+        }
+      })
+      .catch((err) => reject(err))
+  })
+}
+
+const estimateGas =(template) => {
+  return chainClient().transactions.estimateGas(template)
+}
+
+const buildTransaction = (builderBlock) =>{
+  const builderFunction = ( builder ) => {
+    const processed = preprocessTransaction(builderBlock)
+
+    builder.actions = processed.actions
+    builder.ttl = builderBlock.ttl
+    if (processed.baseTransaction) {
+      builder.baseTransaction = processed.baseTransaction
+    }
+
+  }
+  return chainClient().transactions.build(builderFunction)
+}
+
 export default {
   ...list,
   ...form,
   decode,
+  getAddresses,
+  estimateGas,
+  buildTransaction
 }
index 0c7654b..dc33aed 100644 (file)
@@ -127,10 +127,12 @@ class AdvancedTxForm extends React.Component {
               title='+ Add action'
               onSelect={this.addActionItem}
             >
-              <MenuItem eventKey='issue'>Issue</MenuItem>
               <MenuItem eventKey='spend_account'>Spend from account</MenuItem>
               <MenuItem eventKey='control_address'>Control with address</MenuItem>
-              <MenuItem eventKey='retire'>Retire</MenuItem>
+              <MenuItem eventKey='vote_output'>Vote Output</MenuItem>
+              <MenuItem eventKey='unvote'>Unvote</MenuItem>
+              <MenuItem eventKey='cross_chain_in'>Cross Chain In</MenuItem>
+              <MenuItem eventKey='cross_chain_out'>Cross Chain Out</MenuItem>
             </DropdownButton>
           </div>
         </FormSection>
@@ -259,6 +261,9 @@ export default withNamespaces('translations') (BaseNew.connect(
       'actions[].outputId',
       'actions[].type',
       'actions[].address',
+      'actions[].vote',
+      'actions[].sourceId',
+      'actions[].sourcePos',
       'actions[].password',
       'submitAction',
       'password'
index 8a53077..b44db6c 100644 (file)
@@ -4,7 +4,10 @@ import styles from './FormActionItem.scss'
 import { btmID } from 'utility/environment'
 import {withNamespaces} from 'react-i18next'
 
-const ISSUE_KEY = 'issue'
+const CROSS_CHAIN_OUT_KEY = 'cross_chain_out'
+const CROSS_CHAIN_IN_KEY = 'cross_chain_in'
+const VOTE_OUTPUT_KEY = 'vote_output'
+const UNVOTE_KEY = 'unvote'
 const SPEND_ACCOUNT_KEY = 'spend_account'
 const SPEND_UNSPENT_KEY = 'spend_account_unspent_output'
 const CONTROL_RECEIVER_KEY = 'control_receiver'
@@ -13,7 +16,10 @@ const RETIRE_ASSET_KEY = 'retire'
 const TRANSACTION_REFERENCE_DATA = 'set_transaction_reference_data'
 
 const actionLabels = {
-  [ISSUE_KEY]: 'Issue',
+  [CROSS_CHAIN_OUT_KEY]: 'Cross Chain Out',
+  [CROSS_CHAIN_IN_KEY]: 'Cross Chain In',
+  [VOTE_OUTPUT_KEY]: 'Vote Output',
+  [UNVOTE_KEY]: 'Unvote',
   [SPEND_ACCOUNT_KEY]: 'Spend from account',
   [SPEND_UNSPENT_KEY]: 'Spend unspent output',
   [CONTROL_RECEIVER_KEY]: 'Control with receiver',
@@ -23,7 +29,10 @@ const actionLabels = {
 }
 
 const visibleFields = {
-  [ISSUE_KEY]: {asset: true, amount: true, password: true},
+  [CROSS_CHAIN_OUT_KEY]: {asset: true, amount: true, address:true},
+  [CROSS_CHAIN_IN_KEY]: {asset: true, amount: true, sourceId:true, sourcePos:true},
+  [VOTE_OUTPUT_KEY]: {asset: true, amount: true, address:true, vote:true},
+  [UNVOTE_KEY]: {account:true, amount: true, asset: true, vote:true},
   [SPEND_ACCOUNT_KEY]: {asset: true, account: true, amount: true, password: true},
   [SPEND_UNSPENT_KEY]: {outputId: true, password: true},
   [CONTROL_RECEIVER_KEY]: {asset: true, receiver: true, amount: true},
@@ -64,6 +73,9 @@ class ActionItem extends React.Component {
       assetAlias,
       password,
       amount,
+      vote,
+      sourceId,
+      sourcePos,
       referenceData } = this.props.fieldProps
 
     const visible = visibleFields[type.value] || {}
@@ -107,6 +119,9 @@ class ActionItem extends React.Component {
           <JsonField title='Receiver' fieldProps={receiver} />}
 
         {visible.address && <TextField title={ t('form.address' )} fieldProps={address} />}
+        {visible.vote && <TextField title={ t('form.vote' )} fieldProps={vote} />}
+        {visible.sourceId && <TextField title={ t('form.sourceId' )} fieldProps={sourceId} />}
+        {visible.sourcePos && <TextField title={ t('form.sourcePos' )} fieldProps={sourcePos} />}
 
         {visible.outputId &&
           <TextField title='Output ID' fieldProps={outputId} />}
index dcd964e..f757461 100644 (file)
@@ -7,7 +7,7 @@ import componentClassNames from 'utility/componentClassNames'
 import Tutorial from 'features/tutorial/components/Tutorial'
 import NormalTxForm from './NormalTransactionForm'
 import AdvancedTxForm from './AdvancedTransactionForm'
-import IssueAssets from './IssueAssets'
+import Vote from './Vote'
 import { withRouter } from 'react-router'
 import {getValues} from 'redux-form'
 import {withNamespaces} from 'react-i18next'
@@ -63,7 +63,19 @@ class Form extends React.Component {
       return !(this.props.advform['actions'].length > 0 ||
       this.props.advform['signTransaction'] ||
       this.props.advform['password'])
-    }else if(this.props.issueAssetSelected){
+    }else if(this.props.voteSelected){
+      const array = [
+        'accountAlias',
+        'accountId',
+        'amount',
+        'nodePubkey'
+      ]
+
+      for (let k in array){
+        if(this.props.voteform[array[k]]){
+          return false
+        }
+      }
       return true
     }
   }
@@ -99,9 +111,9 @@ class Form extends React.Component {
                   {t('transaction.new.advanced')}
                   </button>
                 <button
-                  className={`btn btn-default ${this.props.issueAssetSelected && 'active'}`}
-                  onClick={(e) => this.showForm(e, 'issueAsset')}>
-                  {t('transaction.issue.issueAsset')}
+                  className={`btn btn-default ${this.props.voteSelected && 'active'}`}
+                  onClick={(e) => this.showForm(e, 'vote')}>
+                  {t('transaction.new.vote')}
                   </button>
               </div>
             </div>
@@ -122,8 +134,8 @@ class Form extends React.Component {
                 handleKeyDown={this.handleKeyDown}
               />}
 
-              {this.props.issueAssetSelected &&
-              <IssueAssets
+              {this.props.voteSelected &&
+              <Vote
                 handleKeyDown={this.handleKeyDown}
                 {...this.props}
               />}
@@ -151,10 +163,11 @@ const mapStateToProps = (state, ownProps) => {
     asset: Object.keys(state.asset.items).map(k => state.asset.items[k]),
     normalform: getValues(state.form.NormalTransactionForm),
     advform: getValues(state.form.AdvancedTransactionForm),
+    voteform: getValues(state.form.Vote),
     tutorialVisible: !state.tutorial.location.isVisited,
     normalSelected : ownProps.location.query.type == 'normal' || ownProps.location.query.type == undefined,
     advancedSelected : ownProps.location.query.type == 'advanced',
-    issueAssetSelected : ownProps.location.query.type == 'issueAsset',
+    voteSelected : ownProps.location.query.type == 'vote',
   }
 }
 
diff --git a/src/features/transactions/components/New/Vote.jsx b/src/features/transactions/components/New/Vote.jsx
new file mode 100644 (file)
index 0000000..d0b9375
--- /dev/null
@@ -0,0 +1,218 @@
+import {
+  BaseNew,
+  TextField,
+  Autocomplete,
+  ObjectSelectorField,
+  AmountUnitField,
+  GasField
+} from 'features/shared/components'
+import {chainClient} from 'utility/environment'
+import {reduxForm} from 'redux-form'
+import React from 'react'
+import styles from './New.scss'
+import  TxContainer  from './NewTransactionsContainer/TxContainer'
+import actions from 'actions'
+import VoteConfirmModal from './VoteConfirmModal/VoteConfirmModal'
+import { voteTxActionBuilder} from '../../transactions'
+import {withNamespaces} from 'react-i18next'
+
+class Vote extends React.Component {
+  constructor(props) {
+    super(props)
+    this.connection = chainClient().connection
+    this.state = {
+      estimateGas:null,
+      address:null
+    }
+
+    this.submitWithValidation = this.submitWithValidation.bind(this)
+    this.disableSubmit = this.disableSubmit.bind(this)
+  }
+
+  disableSubmit() {
+    return !(this.state.estimateGas)
+  }
+
+  submitWithValidation(data) {
+    return new Promise((resolve, reject) => {
+      this.props.submitForm(Object.assign({}, data, {state: this.state, form: 'voteTx'}))
+        .then(() => {
+          this.props.closeModal()
+          this.props.destroyForm()
+        })
+        .catch((err) => {
+          if(err.message !== 'PasswordWrong'){
+            this.props.closeModal()
+          }
+          reject({_error: err})
+        })
+    })
+  }
+
+  confirmedTransaction(e){
+    e.preventDefault()
+    this.props.showModal(
+      <VoteConfirmModal
+        cancel={this.props.closeModal}
+        onSubmit={this.submitWithValidation}
+        gas={this.state.estimateGas}
+        btmAmountUnit={this.props.btmAmountUnit}
+      />
+    )
+  }
+
+  estimateNormalTransactionGas() {
+    const transaction = this.props.fields
+    const accountAlias = transaction.accountAlias.value
+    const accountId = transaction.accountId.value
+    const nodePubkey = transaction.nodePubkey.value
+    const amount = Number(transaction.amount.value)
+
+    const {t, i18n} = this.props
+
+    const noAccount = !accountAlias && !accountId
+
+    if ( nodePubkey === '' || amount === 0|| noAccount ) {
+      this.setState({estimateGas: null})
+      return
+    }
+
+
+    this.props.getAddress({accountAlias, accountId}).then(address => {
+      this.setState({address})
+      const actions = voteTxActionBuilder(transaction, Math.pow(10, 7), address)
+
+      return this.props.buildTransaction({actions, ttl: 1}).then(tmp => {
+        return this.props.estimateGasFee(tmp.data).then(resp => {
+          this.setState({estimateGas: Math.ceil(resp.data.totalNeu/100000)*100000})
+        }).catch(err =>{
+          throw err
+        })
+      }).catch(err =>{
+        throw err
+      })
+    }).catch(err =>{
+      this.setState({estimateGas: null, address: null})
+      const errorMsg =  err.code && i18n.exists(`btmError.${err.code}`) && t(`btmError.${err.code}`) || err.msg
+      this.props.showError(new Error(errorMsg))
+    })
+
+  }
+
+  render() {
+    const {
+      fields: {accountId, accountAlias, nodePubkey, amount, gasLevel},
+      error,
+      submitting
+    } = this.props
+    const t = this.props.t;
+    [accountAlias, accountId,nodePubkey, amount].forEach(key => {
+      key.onBlur = this.estimateNormalTransactionGas.bind(this)
+    });
+
+    let submitLabel = t('transaction.vote.submit')
+
+    return (
+          <TxContainer
+            error={error}
+            onSubmit={(e)=>this.confirmedTransaction(e)}
+            submitting={submitting}
+            submitLabel= {submitLabel}
+            disabled={this.disableSubmit()}
+            className={styles.container}
+          >
+          <div className={styles.borderBottom}>
+            <label className={styles.title}>{t('transaction.vote.info')}</label>
+            <div className={`${styles.mainBox} `}>
+              <ObjectSelectorField
+                key='account-selector-field'
+                keyIndex='votetx-account'
+                title={t('form.account')}
+                aliasField={Autocomplete.AccountAlias}
+                fieldProps={{
+                  id: accountId,
+                  alias: accountAlias
+                }}
+              />
+              <div>
+                <TextField
+                  key='asset-selector-field'
+                  keyIndex='votetx-nodePubkey'
+                  title={ t('form.vote')}
+                  fieldProps={nodePubkey}
+                />
+              </div>
+              <div>
+                <AmountUnitField
+                  key='asset-selector-field'
+                  keyIndex='votetx-amount'
+                  title={ t('transaction.vote.voteAmount')}
+                  fieldProps={amount}
+                />
+              </div>
+            </div>
+
+
+            <label className={styles.title}>{t('transaction.normal.selectFee')}</label>
+            <div className={styles.txFeeBox}>
+              <GasField
+                gas={this.state.estimateGas}
+                fieldProps={gasLevel}
+                btmAmountUnit={this.props.btmAmountUnit}
+              />
+              <span className={styles.feeDescription}> {t('transaction.normal.feeDescription')}</span>
+            </div>
+          </div>
+        </TxContainer>
+    )
+  }
+}
+
+const validate = (values, props) => {
+  const errors = {gas: {}}
+  const t = props.t
+
+  // Numerical
+  if (values.amount && !/^\d+(\.\d+)?$/i.test(values.amount)) {
+    errors.amount = ( t('errorMessage.amountError') )
+  }
+  return errors
+}
+
+const mapDispatchToProps = (dispatch) => ({
+  showError: err => dispatch({type: 'ERROR', payload: err}),
+  closeModal: () => dispatch(actions.app.hideModal),
+  showModal: (body) => dispatch(actions.app.showModal(
+    body,
+    actions.app.hideModal,
+    null,
+    {
+      dialog: true,
+      noCloseBtn: true
+    }
+  )),
+  getAddress: actions.transaction.getAddresses,
+  estimateGasFee: actions.transaction.estimateGas,
+  buildTransaction: actions.transaction.buildTransaction,
+  ...BaseNew.mapDispatchToProps('transaction')(dispatch)
+})
+
+export default withNamespaces('translations') (BaseNew.connect(
+  BaseNew.mapStateToProps('transaction'),
+  mapDispatchToProps,
+  reduxForm({
+    form: 'Vote',
+    fields: [
+      'accountAlias',
+      'accountId',
+      'nodePubkey',
+      'amount',
+      'gasLevel',
+    ],
+    validate,
+    initialValues: {
+      gasLevel: '1'
+    },
+    touchOnChange: true
+  })(Vote)
+))
diff --git a/src/features/transactions/components/New/VoteConfirmModal/VoteConfirmModal.jsx b/src/features/transactions/components/New/VoteConfirmModal/VoteConfirmModal.jsx
new file mode 100644 (file)
index 0000000..d43e183
--- /dev/null
@@ -0,0 +1,128 @@
+import React, { Component } from 'react'
+import {reduxForm} from 'redux-form'
+import {
+  PasswordField,
+  ErrorBanner,
+  SubmitIndicator
+} from 'features/shared/components'
+import { btmID } from 'utility/environment'
+import { normalizeBTMAmountUnit } from 'utility/buildInOutDisplay'
+import styles from './VoteConfirmModal.scss'
+import {withNamespaces} from 'react-i18next'
+
+
+class VoteConfirmModal extends Component {
+  constructor(props) {
+    super(props)
+  }
+
+  render() {
+    const {
+      fields: { accountId, accountAlias, nodePubkey, amount, password, gasLevel },
+      handleSubmit,
+      submitting,
+      cancel,
+      error,
+      gas,
+      t,
+      btmAmountUnit,
+    } = this.props
+
+    const fee = Number(gasLevel.value * gas)
+
+    const  Total = Number(amount.value) + fee
+    let submitLabel = t('transaction.vote.confirm')
+
+    const normalize = (value) => {
+      return normalizeBTMAmountUnit(btmID, value, btmAmountUnit)
+    }
+
+    return (
+      <div>
+        <h3>{ t('transaction.vote.confirm') }</h3>
+        <table className={styles.table}>
+          <tbody>
+            <tr>
+              <td className={styles.colLabel}>{ t('form.account') }</td>
+              <td> <span>{accountAlias.value || accountId.value}</span></td>
+            </tr>
+            <tr>
+              <td className={styles.colLabel}>{ t('transaction.vote.for') }</td>
+              <td> <span>{nodePubkey.value}</span> </td>
+            </tr>
+            <tr>
+              <td className={styles.colLabel}>{ t('transaction.vote.voteAmount') }</td>
+              <td> <code>{normalize(amount.value)}</code> </td>
+            </tr>
+
+            <tr>
+              <td className={styles.colLabel}>{t('form.fee')}</td>
+              <td> <code>{normalize(fee)}</code> </td>
+            </tr>
+
+            <tr>
+              <td className={styles.colLabel}>{t('transaction.normal.total')}</td>
+              <td> <code>{normalize(Total)}</code> </td>
+            </tr>
+          </tbody>
+        </table>
+
+        <hr className={styles.hr}/>
+
+        <form onSubmit={handleSubmit}>
+          <div>
+            <label>{t('key.password')}</label>
+            <PasswordField
+              placeholder={t('key.passwordPlaceholder')}
+              fieldProps={password}
+            />
+          </div>
+
+          {error && error.message === 'PasswordWrong' &&
+          <ErrorBanner
+            title={t('form.errorTitle')}
+            error={t('errorMessage.password')} />}
+
+          <div className={styles.btnGroup}>
+            <div>
+              <button type='submit' className='btn btn-primary'
+                      disabled={submitting}>
+                {submitLabel}
+              </button>
+
+              {submitting &&
+              <SubmitIndicator className={styles.submitIndicator} />
+              }
+            </div>
+            <button type='button'  className='btn btn-default' onClick={cancel}>
+              <i/> {t('form.cancel')}
+            </button>
+          </div>
+        </form>
+      </div>
+    )
+
+  }
+}
+
+const validate = values => {
+  const errors = {}
+  if (!values.password) {
+    errors.password = 'Required'
+  }
+  return errors
+}
+
+export default  withNamespaces('translations') (reduxForm({
+  form: 'Vote',
+  fields:[
+    'accountAlias',
+    'accountId',
+    'nodePubkey',
+    'amount',
+    'gasLevel',
+    'password'
+  ],
+  destroyOnUnmount: false,
+  validate
+})(VoteConfirmModal))
\ No newline at end of file
diff --git a/src/features/transactions/components/New/VoteConfirmModal/VoteConfirmModal.scss b/src/features/transactions/components/New/VoteConfirmModal/VoteConfirmModal.scss
new file mode 100644 (file)
index 0000000..457f734
--- /dev/null
@@ -0,0 +1,45 @@
+.submitIndicator{
+  float:right;
+}
+
+.btnGroup{
+  display: flex;
+  >div{
+    width: 50%;
+    button{
+      width: 100%;
+    }
+  }
+
+  >button{
+    width: 50%;
+    margin-left: $gutter-size;
+  }
+}
+
+.hr{
+  margin-top: 11px;
+  margin-bottom: 11px;
+}
+
+.table{
+  word-break: break-all;
+  width: 100%;
+  td{
+    padding: 4px 0px;
+  }
+}
+
+.colLabel{
+  width: 20%;
+  color: $text-strong-color;
+  font-weight: 500;
+}
+
+.unit{
+  overflow: hidden;
+  text-overflow: ellipsis;
+  vertical-align: bottom;
+  width: 130px;
+  display: inline-block;
+}
index fbf53fe..e365800 100644 (file)
@@ -124,3 +124,39 @@ export const issueAssetTxActionBuilder = (transaction, gas,prop) =>{
 
   return actions
 }
+
+export const voteTxActionBuilder = (transaction, gas, address) =>{
+  const accountAlias = transaction.accountAlias.value || transaction.accountAlias
+  const accountId = transaction.accountId.value || transaction.accountId
+  const nodePubkey = transaction.nodePubkey.value || transaction.nodePubkey
+  const amount = transaction.amount.value || transaction.amount
+
+  const spendAction = {
+    accountAlias,
+    accountId,
+    assetId: btmID,
+    amount,
+    type: 'spend_account'
+  }
+
+
+  const gasAction = {
+    accountAlias,
+    accountId,
+    asset_id:btmID,
+    amount: gas,
+    type: 'spend_account'
+  }
+
+  const voteAction ={
+    amount,
+    asset_id:btmID,
+    address,
+    vote: nodePubkey,
+    type:'vote_output'
+  }
+
+  const actions = [spendAction, gasAction, voteAction]
+
+  return actions
+}
index 7e35d7e..fa16a9e 100644 (file)
     "decimals":"Decimals",
     "reissueTitle":"Token reissuable",
     "reissueTrue":"Able to be reissued",
-    "reissueFalse":"Unable to be reissued"
+    "reissueFalse":"Unable to be reissued",
+    "vote": "Node Public Key"
   },
   "xpub":{
     "methodOptions" : {
       "unsaveWarning":"Your work is not saved! Are you sure you want to leave?",
       "new":"New transaction",
       "normal":"Normal",
+      "vote": "Vote",
       "submit":"Submit transaction",
       "advanced" :"Advanced"
     },
       "accountAddress":"Gas Paid Account Address",
       "gasAccount":"Gas Paid Account",
       "signTx":"Sign Transaction"
+    },
+    "vote":{
+      "info":"Vote Information",
+      "voteAmount":"Amount Vote",
+      "for":"Vote for",
+      "submit":"Submit Vote",
+      "confirm":"Confirm Vote"
     }
   },
   "balances":{
index f199475..3baae12 100644 (file)
     "decimals":"精度",
     "reissueTitle":"是否可重复发行",
     "reissueTrue":"可重复发行",
-    "reissueFalse":"不可重复发行"
+    "reissueFalse":"不可重复发行",
+    "vote": "节点公钥"
   },
   "xpub":{
     "methodOptions" : {
       "new":"新建交易",
       "normal":"简单交易",
       "advanced" :"高级交易",
+      "vote": "投票",
       "submit":"提交交易"
     },
     "normal":{
       "accountAddress":"Gas付款账户地址",
       "gasAccount":"Gas付款账户",
       "signTx":"签名提交交易"
+    },
+    "vote":{
+      "info":"投票信息",
+      "voteAmount":"投票数量",
+      "for":"投票给",
+      "submit":"提交投票",
+      "confirm":"确认投票"
     }
   },
   "balances":{