diff --git a/react/src/actions/actions/elections.js b/react/src/actions/actions/elections.js index f1ad320..190aa80 100644 --- a/react/src/actions/actions/elections.js +++ b/react/src/actions/actions/elections.js @@ -165,4 +165,39 @@ export function shepherdElectionsSend(coin, value, sendToAddress, changeAddress, resolve(json); }); }); +} + +export function shepherdElectionsSendMany(coin, targets, change, opreturn) { + return new Promise((resolve, reject) => { + return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx-multiout`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: Config.token, + coin, + targets, + change, + opreturn, + push: true, + verify: false, + vote: true, + }), + }) + .catch((error) => { + console.log(error); + Store.dispatch( + triggerToaster( + 'shepherdElectionsSendMany', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(json); + }); + }); } \ No newline at end of file diff --git a/react/src/actions/actions/electrum.js b/react/src/actions/actions/electrum.js index ccfe5a8..4f08e8b 100644 --- a/react/src/actions/actions/electrum.js +++ b/react/src/actions/actions/electrum.js @@ -362,12 +362,12 @@ export function shepherdElectrumBip39Keys(seed, match, addressdepth, accounts) { }); } -// split test +// split utxo export function shepherdElectrumSplitUtxoPromise(payload) { console.warn(payload); return new Promise((resolve, reject) => { - return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx-test`, { + return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx-split`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.js b/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.js index 0f47c97..4e73f50 100755 --- a/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.js +++ b/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.js @@ -4,6 +4,7 @@ import { translate } from '../../../translate/translate'; import { toggleNotaryElectionsModal, shepherdElectionsSend, + shepherdElectionsSendMany, shepherdElectionsLogout, shepherdElectionsLogin, shepherdElectionsStatus, @@ -35,6 +36,11 @@ class NotaryElectionsModal extends React.Component { pub: null, amount: 0, address: '', + voteType: 'multi', + multiOutAddress1: '', + multiOutAddress2: '', + multiOutAddress3: '', + multiOutAddress4: '', }; this.defaultState = JSON.parse(JSON.stringify(this.state)); this.closeModal = this.closeModal.bind(this); @@ -42,13 +48,16 @@ class NotaryElectionsModal extends React.Component { this.resizeLoginTextarea = this.resizeLoginTextarea.bind(this); this.updateLoginPassPhraseInput = this.updateLoginPassPhraseInput.bind(this); this.setUserType = this.setUserType.bind(this); + this.setVoteType = this.setVoteType.bind(this); this.setRegion = this.setRegion.bind(this); this.loginSeed = this.loginSeed.bind(this); this.logout = this.logout.bind(this); this.sync = this.sync.bind(this); this.send = this.send.bind(this); + this.sendMulti = this.sendMulti.bind(this); this.updateInput = this.updateInput.bind(this); this.sendValidate = this.sendValidate.bind(this); + this.verifyMultiSendForm = this.verifyMultiSendForm.bind(this); this.electionsDataInterval = null; } @@ -105,20 +114,52 @@ class NotaryElectionsModal extends React.Component { return valid; } - send() { - if (this.sendValidate()) { - shepherdElectionsSend( + sendMulti() { + const _divisor = 10; + let _addressValidateMsg = []; + + for (let i = 0; i < 4; i++) { + const _validateAddress = mainWindow.addressVersionCheck(this.state.coin, this.state[`multiOutAddress${i + 1}`]); + + if (!_validateAddress || + _validateAddress === 'Invalid pub address') { + _addressValidateMsg.push(this.state[`multiOutAddress${i + 1}`]); + } + } + + if (_addressValidateMsg.length) { + Store.dispatch( + triggerToaster( + `Address(es) ${_addressValidateMsg.join(', ')} is(are) invalid!`, + 'Notary voting 2018', + 'error', + false + ) + ); + } else { + shepherdElectionsSendMany( this.state.coin, - (this.state.amount * 100000000) - 10000, - this.state.address, + [{ + address: this.state.multiOutAddress1, + value: parseInt((this.state.balance / _divisor) * 100000000), + }, { + address: this.state.multiOutAddress2, + value: parseInt((this.state.balance / _divisor) * 100000000), + }, { + address: this.state.multiOutAddress3, + value: parseInt((this.state.balance / _divisor) * 100000000), + }, { + address: this.state.multiOutAddress4, + value: parseInt((this.state.balance / _divisor) * 100000000), + }], this.state.pub, - 'ne2k18-' + this.state.region, + ['ne2k18-na-1-eu-2-ae-3-sh-4'] ) .then((res) => { if (res.msg === 'success') { Store.dispatch( triggerToaster( - `You succesfully voted ${this.state.amount} for ${this.state.address}`, + `You succesfully voted`, 'Notary voting 2018', 'success', false @@ -126,13 +167,29 @@ class NotaryElectionsModal extends React.Component { ); let _transactions = this.state.transactions; _transactions.unshift({ - address: this.state.address, - amount: this.state.amount - 0.0001, - region: 'ne2k18-' + this.state.region, + address: this.state.multiOutAddress1, + amount: this.state.balance / _divisor, + region: 'ne2k18-na', + timestamp: Math.floor(Date.now() / 1000), + }, { + address: this.state.multiOutAddress2, + amount: this.state.balance / _divisor, + region: 'ne2k18-eu', + timestamp: Math.floor(Date.now() / 1000), + }, { + address: this.state.multiOutAddress3, + amount: this.state.balance / _divisor, + region: 'ne2k18-ae', + timestamp: Math.floor(Date.now() / 1000), + }, { + address: this.state.multiOutAddress4, + amount: this.state.balance / _divisor, + region: 'ne2k18-sh', timestamp: Math.floor(Date.now() / 1000), }); this.setState({ transactions: _transactions, + balance: 0, }); } else { Store.dispatch( @@ -147,6 +204,64 @@ class NotaryElectionsModal extends React.Component { } } + send() { + const _validateAddress = mainWindow.addressVersionCheck(this.state.coin, this.state.address); + + if (!_validateAddress || + _validateAddress === 'Invalid pub address') { + _addressValidateMsg.push(this.state[`multiOutAddress${i + 1}`]); + Store.dispatch( + triggerToaster( + `Address ${this.state.address} is invalid!`, + 'Notary voting 2018', + 'error', + false + ) + ); + } else { + if (this.sendValidate()) { + shepherdElectionsSend( + this.state.coin, + (this.state.amount * 100000000) - 10000, + this.state.address, + this.state.pub, + 'ne2k18-' + this.state.region, + ) + .then((res) => { + if (res.msg === 'success') { + Store.dispatch( + triggerToaster( + `You succesfully voted ${this.state.amount} for ${this.state.address}`, + 'Notary voting 2018', + 'success', + false + ) + ); + let _transactions = this.state.transactions; + _transactions.unshift({ + address: this.state.address, + amount: this.state.amount - 0.0001, + region: 'ne2k18-' + this.state.region, + timestamp: Math.floor(Date.now() / 1000), + }); + this.setState({ + transactions: _transactions, + balance: this.state.balance - this.state.amount - 0.0001, + }); + } else { + Store.dispatch( + triggerToaster( + res.result.txid || res.result, + 'Notary voting 2018', + 'error' + ) + ); + } + }); + } + } + } + sync() { shepherdElectionsStatus() .then((res) => { @@ -216,6 +331,12 @@ class NotaryElectionsModal extends React.Component { }); } + setVoteType(type) { + this.setState({ + voteType: type, + }); + } + toggleSeedInputVisibility() { this.setState({ seedInputVisibility: !this.state.seedInputVisibility, @@ -305,6 +426,17 @@ class NotaryElectionsModal extends React.Component { } } + verifyMultiSendForm() { + if (!this.state.multiOutAddress1 || !this.state.multiOutAddress1.length || + !this.state.multiOutAddress2 || !this.state.multiOutAddress2.length || + !this.state.multiOutAddress3 || !this.state.multiOutAddress3.length || + !this.state.multiOutAddress4 || !this.state.multiOutAddress4.length) { + return false; + } else { + return true; + } + } + renderHistoryRegion(region) { let _region; @@ -401,13 +533,13 @@ class NotaryElectionsModal extends React.Component { | this.setUserType('candidate') }>I'm a candidate + onClick={ () => this.setUserType('candidate') }>I'm a candidate { !this.state.isAuth &&
+ htmlFor="inputPassword">{ this.state.userType === 'voter' ? translate('INDEX.WALLET_SEED') : 'Pub key' } -
+
@@ -479,9 +611,22 @@ class NotaryElectionsModal extends React.Component { You have { this.state.balance } VOTE
} + { this.state.isAuth && +
+ this.setVoteType('multi') }>4-way vote + | + this.setVoteType('single') }>1-way vote +
+ } { this.state.isAuth && this.state.userType === 'voter' && -
+ this.state.voteType === 'single' && + this.state.balance > 0 && +
} + { this.state.isAuth && + this.state.userType === 'voter' && + this.state.voteType === 'multi' && + this.state.balance > 0 && +
+
Each candidate is going to recieve 25% of your VOTE funds
+
+ + + { this.state.balance / 4 } VOTE +
+
+ + + { this.state.balance / 4 } VOTE +
+
+ + + { this.state.balance / 4 } VOTE +
+
+ + + { this.state.balance / 4 } VOTE +
+ +
+ } { this.displayTxHistoryRender() &&
diff --git a/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.scss b/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.scss index 36740fa..813305c 100644 --- a/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.scss +++ b/react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.scss @@ -65,6 +65,10 @@ left: 0; height: 350px; text-align: center; + + &.disable { + pointer-events: none; + } } .elections-map-node { @@ -154,18 +158,27 @@ .elections-send { .form-control { display: inline-block; - width: 250px; - } - .btn { - position: relative; - top: -6px; - left: 15px; - display: inline-block; + width: 350px; } .block { - width: 365px; + width: 400px; display: block; } + + &.elections-send--single { + .btn { + position: relative; + top: -6px; + left: 15px; + display: inline-block; + } + .form-control { + width: 250px; + } + .block { + width: 365px; + } + } } }