From 7263be0f5a9bf169363714b648a1497e6a54e7c7 Mon Sep 17 00:00:00 2001 From: pbca26 Date: Thu, 23 Nov 2017 21:41:18 +0300 Subject: [PATCH] claim interest modal address dropdown, spv --- react/src/actions/actions/electrum.js | 54 ++++ .../claimInterestModal/claimInterestModal.js | 266 +++++++++++++++--- .../claimInterestModal.render.js | 68 ++++- .../claimInterestModal.scss | 6 +- .../components/dashboard/sendCoin/sendCoin.js | 1 + .../dashboard/walletsData/walletsData.js | 3 +- 6 files changed, 348 insertions(+), 50 deletions(-) diff --git a/react/src/actions/actions/electrum.js b/react/src/actions/actions/electrum.js index 6e27239..412b7a0 100644 --- a/react/src/actions/actions/electrum.js +++ b/react/src/actions/actions/electrum.js @@ -233,6 +233,31 @@ export function shepherdElectrumSend(coin, value, sendToAddress, changeAddress) } } +export function shepherdElectrumSendPromise(coin, value, sendToAddress, changeAddress) { + return new Promise((resolve, reject) => { + return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=true&verify=true`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + } + }) + .catch((error) => { + console.log(error); + dispatch( + triggerToaster( + 'shepherdElectrumSendPromise', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(json); + }); + }); +} + export function shepherdElectrumSendPreflight(coin, value, sendToAddress, changeAddress) { return new Promise((resolve, reject) => { fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=false&verify=true`, { @@ -256,4 +281,33 @@ export function shepherdElectrumSendPreflight(coin, value, sendToAddress, change resolve(json); }); }); +} + +export function shepherdElectrumListunspent(coin, address) { + return new Promise((resolve, reject) => { + fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/listunspent?coin=${coin}&address=${address}&full=true`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .catch((error) => { + console.log(error); + dispatch( + triggerToaster( + 'shepherdElectrumListunspent', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + if (!json.result) { + resolve('error'); + } else { + resolve(json); + } + }); + }); } \ No newline at end of file diff --git a/react/src/components/dashboard/claimInterestModal/claimInterestModal.js b/react/src/components/dashboard/claimInterestModal/claimInterestModal.js index 0023587..5f4fb09 100755 --- a/react/src/components/dashboard/claimInterestModal/claimInterestModal.js +++ b/react/src/components/dashboard/claimInterestModal/claimInterestModal.js @@ -9,6 +9,9 @@ import { copyString, sendToAddressPromise, triggerToaster, + shepherdElectrumListunspent, + shepherdElectrumSendPreflight, + shepherdElectrumSendPromise, } from '../../../actions/actionCreators'; import { translate } from '../../../translate/translate'; import { @@ -27,11 +30,20 @@ class ClaimInterestModal extends React.Component { transactionsList: [], showZeroInterest: true, totalInterest: 0, + spvPreflightSendInProgress: false, + spvVerificationWarning: false, + addressses: {}, + addressSelectorOpen: false, + selectedAddress: null, }; this.claimInterestTableRender = this.claimInterestTableRender.bind(this); this.toggleZeroInterest = this.toggleZeroInterest.bind(this); this.loadListUnspent = this.loadListUnspent.bind(this); this.checkTransactionsListLength = this.checkTransactionsListLength.bind(this); + this.cancelClaimInterest = this.cancelClaimInterest.bind(this); + this.openDropMenu = this.openDropMenu.bind(this); + this.closeDropMenu = this.closeDropMenu.bind(this); + this.closeModal = this.closeModal.bind(this); } componentWillMount() { @@ -40,25 +52,52 @@ class ClaimInterestModal extends React.Component { } } + openDropMenu() { + this.setState(Object.assign({}, this.state, { + addressSelectorOpen: !this.state.addressSelectorOpen, + })); + } + + closeDropMenu() { + if (this.state.addressSelectorOpen) { + setTimeout(() => { + this.setState(Object.assign({}, this.state, { + addressSelectorOpen: false, + })); + }, 100); + } + } + + updateAddressSelection(address) { + this.setState(Object.assign({}, this.state, { + selectedAddress: address, + addressSelectorOpen: !this.state.addressSelectorOpen, + })); + } + loadListUnspent() { let _transactionsList = []; let _totalInterest = 0; - getListUnspent(this.props.ActiveCoin.coin) - .then((json) => { - if (json && - json.length) { - for (let i = 0; i < json.length; i++) { - getRawTransaction(this.props.ActiveCoin.coin, json[i].txid) - .then((_json) => { + if (this.props.ActiveCoin.mode === 'spv') { + shepherdElectrumListunspent( + this.props.ActiveCoin.coin, + this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub + ).then((json) => { + if (json !== 'error' && + json.result && + typeof json.result !== 'string') { + json = json.result; + + for (let i = 0; i < json.length; i++) { _transactionsList.push({ address: json[i].address, - locktime: _json.locktime, - amount: json[i].amount, - interest: json[i].interest, + locktime: json[i].locktime, + amount: Number(json[i].amount.toFixed(8)), + interest: Number(json[i].interest.toFixed(8)), txid: json[i].txid, }); - _totalInterest += Number(json[i].interest); + _totalInterest += Number(json[i].interest.toFixed(8)); if (i === json.length - 1) { this.setState({ @@ -67,39 +106,148 @@ class ClaimInterestModal extends React.Component { totalInterest: _totalInterest, }); } + } + } else { + this.setState({ + transactionsList: [], + isLoading: false, + totalInterest: 0, }); } + }); + } else { + getListUnspent(this.props.ActiveCoin.coin) + .then((json) => { + if (json && + json.length) { + let _addresses = {}; + + for (let i = 0; i < json.length; i++) { + getRawTransaction(this.props.ActiveCoin.coin, json[i].txid) + .then((_json) => { + _addresses[json[i].address] = json[i].address; + _transactionsList.push({ + address: json[i].address, + locktime: _json.locktime, + amount: json[i].amount, + interest: json[i].interest, + txid: json[i].txid, + }); + _totalInterest += Number(json[i].interest); + + if (i === json.length - 1) { + this.setState({ + transactionsList: _transactionsList, + isLoading: false, + totalInterest: _totalInterest, + addressses: _addresses, + selectedAddress: this.state.selectedAddress ? this.state.selectedAddress : _addresses[Object.keys(_addresses)[0]], + }); + } + }); + } + } + }); + } + } + + cancelClaimInterest() { + this.setState(Object.assign({}, this.state, { + spvVerificationWarning: false, + spvPreflightSendInProgress: false, + })); + } + + confirmClaimInterest() { + shepherdElectrumSendPromise( + this.props.ActiveCoin.coin, + this.props.ActiveCoin.balance.balanceSats, + this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub, + this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub + ).then((res) => { + if (res.msg === 'error') { + Store.dispatch( + triggerToaster( + res.result, + 'Error', + 'error' + ) + ); + } else { + Store.dispatch( + triggerToaster( + `${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P1')} ${this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub}. ${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P2')}`, + translate('TOASTR.WALLET_NOTIFICATION'), + 'success', + false + ) + ); + this.setState({ + transactionsList: [], + isLoading: false, + totalInterest: 0, + }); } }); } claimInterest(address, amount) { if (this.props.ActiveCoin.coin === 'KMD') { - sendToAddressPromise( - this.props.ActiveCoin.coin, - this.state.transactionsList[0].address, - this.props.ActiveCoin.balance.transparent - ).then((json) => { - if (json.error && - json.error.code) { - Store.dispatch( - triggerToaster( - json.error.message, - 'Error', - 'error' - ) - ); - } else if (json.result && json.result.length && json.result.length === 64) { - Store.dispatch( - triggerToaster( - `${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P1')} ${this.state.transactionsList[0].address}. ${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P2')}`, - translate('TOASTR.WALLET_NOTIFICATION'), - 'success', - false - ) - ); - } - }); + if (this.props.ActiveCoin.mode === 'spv') { + this.setState(Object.assign({}, this.state, { + spvVerificationWarning: false, + spvPreflightSendInProgress: true, + })); + + shepherdElectrumSendPreflight( + this.props.ActiveCoin.coin, + this.props.ActiveCoin.balance.balanceSats, + this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub, + this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub + ).then((sendPreflight) => { + if (sendPreflight && + sendPreflight.msg === 'success') { + this.setState(Object.assign({}, this.state, { + spvVerificationWarning: !sendPreflight.result.utxoVerified, + spvPreflightSendInProgress: false, + })); + + if (sendPreflight.result.utxoVerified) { + this.confirmClaimInterest(); + } + } else { + this.setState(Object.assign({}, this.state, { + spvPreflightSendInProgress: false, + })); + } + }); + } else { + sendToAddressPromise( + this.props.ActiveCoin.coin, + this.state.selectedAddress, // this.state.transactionsList[0].address, + this.props.ActiveCoin.balance.transparent + ).then((json) => { + if (json.error && + json.error.code) { + Store.dispatch( + triggerToaster( + json.error.message, + 'Error', + 'error' + ) + ); + } else if (json.result && json.result.length && json.result.length === 64) { + Store.dispatch( + triggerToaster( + `${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P1')} ${this.state.transactionsList[0].address}. ${translate('TOASTR.CLAIM_INTEREST_BALANCE_SENT_P2')}`, + translate('TOASTR.WALLET_NOTIFICATION'), + 'success', + false + ) + ); + } + }); + } } } @@ -126,6 +274,46 @@ class ClaimInterestModal extends React.Component { return _ClaimInterestTableRender.call(this); } + addressDropdownRender() { + let _items = []; + + for (let key in this.state.addressses) { + _items.push( +
  • + this.updateAddressSelection(key) }> + { key } + + +
  • + ); + } + + return ( +
    + +
    +
      + { _items } +
    +
    +
    + ); + } + componentWillReceiveProps(props) { if (props.Dashboard.displayClaimInterestModal !== this.state.open) { this.setState({ @@ -140,6 +328,10 @@ class ClaimInterestModal extends React.Component { } closeModal() { + this.setState({ + addressses: {}, + selectedAddress: null, + }); Store.dispatch(toggleClaimInterestModal(false)); } @@ -157,12 +349,14 @@ class ClaimInterestModal extends React.Component { const mapStateToProps = (state) => { return { ActiveCoin: { + mode: state.ActiveCoin.mode, coin: state.ActiveCoin.coin, balance: state.ActiveCoin.balance, activeSection: state.ActiveCoin.activeSection, }, Dashboard: { displayClaimInterestModal: state.Dashboard.displayClaimInterestModal, + electrumCoins: state.Dashboard.electrumCoins, }, }; }; diff --git a/react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js b/react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js index a423385..cb8d9dc 100644 --- a/react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js +++ b/react/src/components/dashboard/claimInterestModal/claimInterestModal.render.js @@ -23,10 +23,14 @@ export const _ClaimInterestTableRender = function() { { _transactionsList[i].interest } { _transactionsList[i].locktime && - + } { !_transactionsList[i].locktime && - + } @@ -59,12 +63,52 @@ export const _ClaimInterestTableRender = function() { onClick={ this.toggleZeroInterest }> { translate('CLAIM_INTEREST.SHOW_ZERO_INTEREST') } - + { !this.state.spvVerificationWarning && + + } + { this.state.spvVerificationWarning && +
    + { translate('SEND.WARNING') }: { translate('SEND.WARNING_SPV_P1') } { translate('SEND.WARNING_SPV_P2') } +
    + + +
    +
    + } + { this.props.ActiveCoin.mode === 'native' && + this.state.addressses && + Object.keys(this.state.addressses).length > 0 && +
    +
    Send my balance to
    + { this.addressDropdownRender() } +
    + } }
    @@ -98,7 +142,7 @@ export const _ClaimInterestTableRender = function() { export const ClaimInterestModalRender = function() { return ( - +
    @@ -120,10 +164,12 @@ export const ClaimInterestModalRender = function() { { this.state.isLoading && { translate('INDEX.LOADING') }... } - { !this.state.isLoading && this.checkTransactionsListLength() && + { !this.state.isLoading && + this.checkTransactionsListLength() &&
    { this.claimInterestTableRender() }
    } - { !this.state.isLoading && !this.checkTransactionsListLength() && + { !this.state.isLoading && + !this.checkTransactionsListLength() &&
    { translate('INDEX.NO_DATA') }
    }
    diff --git a/react/src/components/dashboard/claimInterestModal/claimInterestModal.scss b/react/src/components/dashboard/claimInterestModal/claimInterestModal.scss index b48cc1d..52d5816 100644 --- a/react/src/components/dashboard/claimInterestModal/claimInterestModal.scss +++ b/react/src/components/dashboard/claimInterestModal/claimInterestModal.scss @@ -16,7 +16,7 @@ margin-bottom: 30px; } .table-scroll { - height: 366px; + max-height: 366px; overflow-y: auto; overflow-x: hidden; width: 100%; @@ -43,5 +43,9 @@ font-size: 20px; z-index: 100; } + + .bootstrap-select { + max-width: 400px; + } } } \ No newline at end of file diff --git a/react/src/components/dashboard/sendCoin/sendCoin.js b/react/src/components/dashboard/sendCoin/sendCoin.js index dee4c64..0a85b23 100644 --- a/react/src/components/dashboard/sendCoin/sendCoin.js +++ b/react/src/components/dashboard/sendCoin/sendCoin.js @@ -23,6 +23,7 @@ import { isPositiveNumber } from '../../../util/number'; // TODO: - add links to explorers // - render z address trim +// - handle click outside class SendCoin extends React.Component { constructor(props) { diff --git a/react/src/components/dashboard/walletsData/walletsData.js b/react/src/components/dashboard/walletsData/walletsData.js index 02f97a1..7b80511 100644 --- a/react/src/components/dashboard/walletsData/walletsData.js +++ b/react/src/components/dashboard/walletsData/walletsData.js @@ -88,12 +88,11 @@ class WalletsData extends React.Component { displayClaimInterestUI() { if (this.props.ActiveCoin && this.props.ActiveCoin.coin === 'KMD' && - this.props.ActiveCoin.mode === 'native' && this.props.ActiveCoin.balance) { if (this.props.ActiveCoin.balance.interest && this.props.ActiveCoin.balance.interest > 0) { return 777; - } else if (this.props.ActiveCoin.balance.transparent && this.props.ActiveCoin.balance.transparent >= 10) { + } else if ((this.props.ActiveCoin.balance.transparent && this.props.ActiveCoin.balance.transparent >= 10) || (this.props.ActiveCoin.balance.balance && this.props.ActiveCoin.balance.balance >= 10)) { return -777; } }