From 39907aff5484ae82fe9b330e0fd372622a01be13 Mon Sep 17 00:00:00 2001 From: pbca26 Date: Sun, 28 Jan 2018 18:11:41 +0300 Subject: [PATCH 1/5] tools native utxo split --- react/src/actions/actions/cli.js | 8 +- react/src/actions/actions/electrum.js | 32 +++ react/src/components/dashboard/tools/tools.js | 247 ++++++++++++++++++ 3 files changed, 285 insertions(+), 2 deletions(-) diff --git a/react/src/actions/actions/cli.js b/react/src/actions/actions/cli.js index c770295..1591b74 100644 --- a/react/src/actions/actions/cli.js +++ b/react/src/actions/actions/cli.js @@ -3,14 +3,18 @@ import { CLI } from '../storeType'; import Config from '../../config'; import Store from '../../store'; -export function shepherdCliPromise(mode, chain, cmd) { - const _payload = { +export function shepherdCliPromise(mode, chain, cmd, params) { + let _payload = { mode, chain, cmd, token: Config.token, }; + if (params) { + _payload.params = params; + } + return new Promise((resolve, reject) => { fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/cli`, { method: 'POST', diff --git a/react/src/actions/actions/electrum.js b/react/src/actions/actions/electrum.js index 580c72c..584392c 100644 --- a/react/src/actions/actions/electrum.js +++ b/react/src/actions/actions/electrum.js @@ -334,4 +334,36 @@ export function shepherdElectrumBip39Keys(seed, match, addressdepth, accounts) { resolve(json); }); }); +} + +// split test +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`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + payload, + token: Config.token, + }), + }) + .catch((error) => { + console.log(error); + Store.dispatch( + triggerToaster( + 'shepherdElectrumSendPromise', + 'Error', + 'error' + ) + ); + }) + .then(response => response.json()) + .then(json => { + resolve(json); + }); + }); } \ No newline at end of file diff --git a/react/src/components/dashboard/tools/tools.js b/react/src/components/dashboard/tools/tools.js index ee1090b..7d36ddf 100644 --- a/react/src/components/dashboard/tools/tools.js +++ b/react/src/components/dashboard/tools/tools.js @@ -11,6 +11,8 @@ import { shepherdToolsSeedToWif, shepherdToolsWifToKP, shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, } from '../../../actions/actionCreators'; import Store from '../../../store'; import QRCode from 'qrcode.react'; @@ -43,6 +45,12 @@ class Tools extends React.Component { balanceAddr: '', balanceCoin: '', balanceResult: null, + utxoSplitCoin: 'BEER', + utxoSplitList: null, + utxoSplitPairsCount: 1, + utxoSplitPairs: '10,0.002', + utxoSplitRawtx: null, + utxoSplitPushResult: null, }; this.updateInput = this.updateInput.bind(this); this.updateSelectedCoin = this.updateSelectedCoin.bind(this); @@ -56,6 +64,114 @@ class Tools extends React.Component { this.getBalanceAlt = this.getBalanceAlt.bind(this); this.getUtxos = this.getUtxos.bind(this); this.toggleS2wIsIguana = this.toggleS2wIsIguana.bind(this); + this.getUtxoSplit = this.getUtxoSplit.bind(this); + this.splitUtxo = this.splitUtxo.bind(this); + } + + splitUtxo() { + let largestUTXO = { amount: 0 }; + + for (let i = 0; i < this.state.utxoSplitList.length; i++) { + if (Number(this.state.utxoSplitList[i].amount) > Number(largestUTXO.amount)) { + largestUTXO = this.state.utxoSplitList[i]; + } + } + + console.warn(`largest utxo ${largestUTXO.amount}`); + console.warn(`largest utxo ${largestUTXO.amount}`); + + const utxoSize = largestUTXO.amount; + const targetSizes = this.state.utxoSplitPairs.split(','); + const wif = ''; + const address = ''; + let totalOutSize = 0; + let _targets = []; + + console.warn(`total utxos ${this.state.utxoSplitPairsCount * targetSizes.length}`); + console.warn(`total pairs ${this.state.utxoSplitPairsCount}`); + console.warn(`utxo size ${utxoSize}`); + console.warn(`utxo sizes`); + console.warn(targetSizes); + + for (let i = 0; i < this.state.utxoSplitPairsCount; i++) { + console.warn(`vout ${i} ${targetSizes[0]}`); + console.warn(`vout ${i + 1} ${targetSizes[1]}`); + _targets.push(Number(targetSizes[0]) * 100000000); + _targets.push(Number(targetSizes[1]) * 100000000); + totalOutSize += Number(targetSizes[0]) + Number(targetSizes[1]); + } + + console.warn(`total out size ${totalOutSize}`); + console.warn(`change ${utxoSize - totalOutSize}`); + + const payload = { + wif, + network: 'komodo', + targets: _targets, + utxo: [largestUTXO], + changeAddress: address, + outputAddress: address, + change: Math.floor(Number(utxoSize - totalOutSize) * 100000000) - 10000, // 10k sat fee + }; + + shepherdElectrumSplitUtxoPromise(payload) + .then((res) => { + console.warn(res); + + if (res.msg === 'success') { + //this.setState({ + // utxoSplitRawtx: res.result, + //}); + + shepherdCliPromise(null, this.state.utxoSplitCoin, 'sendrawtransaction', [res.result]) + .then((res) => { + console.warn(res); + + if (!res.error) { + this.setState({ + utxoSplitPushResult: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Split UTXO error', + 'error' + ) + ); + } + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Split UTXO error', + 'error' + ) + ); + } + }); + } + + getUtxoSplit() { + shepherdCliPromise(null, this.state.utxoSplitCoin, 'listunspent') + .then((res) => { + console.warn(res); + + if (!res.error) { + this.setState({ + utxoSplitList: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Get UTXO error', + 'error' + ) + ); + } + }); } getUtxos() { @@ -325,6 +441,53 @@ class Tools extends React.Component { ); } + renderUTXOSplitResponse() { + const _utxos = this.state.utxoSplitList; + let _items = []; + console.warn(_utxos); + + if (_utxos && + _utxos.length) { + for (let i = 0; i < _utxos.length; i++) { + _items.push( + + { _utxos[i].amount } + { _utxos[i].address } + { _utxos[i].confirmations } + { _utxos[i].vout } + { _utxos[i].txid } + + ); + } + } + + return ( + + + + + + + + + + + + { _items } + + + + + + + + + + +
AmountAddressConfirmationsVoutTxID
AmountAddressConfirmationsVoutTxID
+ ); + } + render() { return (
@@ -368,6 +531,11 @@ class Tools extends React.Component { onClick={ () => this.setActiveSection('utxo') }> UTXO * +
* Electrum

@@ -758,6 +926,85 @@ class Tools extends React.Component { } } + { this.state.activeSection === 'utxo-split' && +
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ +
+ { this.state.utxoSplitList && +
+ { /*this.renderUTXOSplitResponse()*/ } + Total UTXO: { this.state.utxoSplitList.length } +
+ } + { this.state.utxoSplitRawtx && +
+ Rawtx:
{ this.state.utxoSplitRawtx }
+
+ } + { this.state.utxoSplitPushResult && +
+ TXID:
{ this.state.utxoSplitPushResult }
+
+ } +
+ } From 8965e0341af432e1e3a812799f045cb9ad592b93 Mon Sep 17 00:00:00 2001 From: pbca26 Date: Tue, 30 Jan 2018 19:25:52 +0300 Subject: [PATCH 2/5] tool native split/merge utxo --- react/src/components/dashboard/tools/tools.js | 618 ++++++++++++++++-- 1 file changed, 558 insertions(+), 60 deletions(-) diff --git a/react/src/components/dashboard/tools/tools.js b/react/src/components/dashboard/tools/tools.js index 7d36ddf..b22e26e 100644 --- a/react/src/components/dashboard/tools/tools.js +++ b/react/src/components/dashboard/tools/tools.js @@ -17,6 +17,7 @@ import { import Store from '../../../store'; import QRCode from 'qrcode.react'; import QRModal from '../qrModal/qrModal'; +import { isKomodoCoin } from '../../../util/coinHelper'; class Tools extends React.Component { constructor() { @@ -32,25 +33,45 @@ class Tools extends React.Component { rawTx2Push: null, txPushResult: null, string2qr: null, + // seed 2 wif s2wSeed: '', s2wCoin: '', s2wisIguana: true, s2wResult: null, + // wif 2 wif w2wWif: '', w2wCoin: '', w2wResult: null, + // utxo list utxoAddr: '', utxoCoin: '', utxoResult: null, + // balance balanceAddr: '', balanceCoin: '', balanceResult: null, - utxoSplitCoin: 'BEER', + // utxo split + utxoSplitLargestUtxo: null, + utxoSplitAddress: null, + utxoSplitWif: null, + utxoSplitSeed: '', + utxoSplitCoin: '', utxoSplitList: null, utxoSplitPairsCount: 1, utxoSplitPairs: '10,0.002', utxoSplitRawtx: null, utxoSplitPushResult: null, + utxoSplitShowUtxoList: false, + // utxo merge + utxoMergeAddress: null, + utxoMergeWif: null, + utxoMergeSeed: '', + utxoMergeCoin: '', + utxoMergeUtxoNum: 10, + utxoMergeRawtx: null, + utxoMergeList: null, + utxoMergePushResult: null, + utxoMergeShowUtxoList: false, }; this.updateInput = this.updateInput.bind(this); this.updateSelectedCoin = this.updateSelectedCoin.bind(this); @@ -66,6 +87,106 @@ class Tools extends React.Component { this.toggleS2wIsIguana = this.toggleS2wIsIguana.bind(this); this.getUtxoSplit = this.getUtxoSplit.bind(this); this.splitUtxo = this.splitUtxo.bind(this); + this.getUtxoMerge = this.getUtxoMerge.bind(this); + this.mergeUtxo = this.mergeUtxo.bind(this); + this.toggleMergeUtxoList = this.toggleMergeUtxoList.bind(this); + this.toggleSplitUtxoList = this.toggleSplitUtxoList.bind(this); + } + + toggleMergeUtxoList() { + this.setState({ + utxoMergeShowUtxoList: !this.state.utxoMergeShowUtxoList, + }); + } + + toggleSplitUtxoList() { + this.setState({ + utxoSplitShowUtxoList: !this.state.utxoSplitShowUtxoList, + }); + } + + mergeUtxo() { + const wif = this.state.utxoMergeWif; + const address = this.state.utxoMergeAddress; + const utxoNum = this.state.utxoMergeUtxoNum; + let totalOutSize = 0; + let _utxos = []; + let _interest = 0; + + for (let i = 0; i < utxoNum; i++) { + console.warn(`vout ${i} ${this.state.utxoMergeList[i].amount}`); + _utxos.push(JSON.parse(JSON.stringify(this.state.utxoMergeList[i]))); + totalOutSize += Number(this.state.utxoMergeList[i].amount); + } + + for (let i = 0; i < _utxos.length; i++) { + _utxos[i].amount = Number(_utxos[i].amount) * 100000000; + _utxos[i].interest = Number(_utxos[i].interest) * 100000000; + _interest += _utxos[i].interest; + } + + console.warn(`total out size ${totalOutSize}`); + console.warn(`interest ${_interest}`); + + const payload = { + wif, + network: 'komodo', + targets: [Math.floor(totalOutSize * 100000000) - 10000 + _interest], + utxo: _utxos, + changeAddress: address, + outputAddress: address, + change: 0, + }; + + console.log(payload); + + shepherdElectrumSplitUtxoPromise(payload) + .then((res) => { + console.warn(res); + + if (res.msg === 'success') { + const _coin = this.state.utxoMergeCoin.split('|'); + + shepherdCliPromise( + null, + _coin[0], + 'sendrawtransaction', + [res.result] + ) + .then((res) => { + console.warn(res); + + if (!res.error) { + this.setState({ + utxoMergePushResult: res.result, + }); + Store.dispatch( + triggerToaster( + 'Merge success', + 'UTXO', + 'success' + ) + ); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Split UTXO error', + 'error' + ) + ); + } + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Split UTXO error', + 'error' + ) + ); + } + }); } splitUtxo() { @@ -82,27 +203,28 @@ class Tools extends React.Component { const utxoSize = largestUTXO.amount; const targetSizes = this.state.utxoSplitPairs.split(','); - const wif = ''; - const address = ''; + const wif = this.state.utxoSplitWif; + const address = this.state.utxoSplitAddress; + const pairsCount = this.state.utxoSplitPairsCount; let totalOutSize = 0; let _targets = []; - console.warn(`total utxos ${this.state.utxoSplitPairsCount * targetSizes.length}`); - console.warn(`total pairs ${this.state.utxoSplitPairsCount}`); + console.warn(`total utxos ${pairsCount * targetSizes.length}`); + console.warn(`total pairs ${pairsCount}`); console.warn(`utxo size ${utxoSize}`); console.warn(`utxo sizes`); console.warn(targetSizes); - for (let i = 0; i < this.state.utxoSplitPairsCount; i++) { - console.warn(`vout ${i} ${targetSizes[0]}`); - console.warn(`vout ${i + 1} ${targetSizes[1]}`); - _targets.push(Number(targetSizes[0]) * 100000000); - _targets.push(Number(targetSizes[1]) * 100000000); - totalOutSize += Number(targetSizes[0]) + Number(targetSizes[1]); + for (let i = 0; i < pairsCount; i++) { + for (let j = 0; j < targetSizes.length; j++) { + console.warn(`vout ${_targets.length} ${targetSizes[j]}`); + _targets.push(Number(targetSizes[j]) * 100000000); + totalOutSize += Number(targetSizes[j]); + } } console.warn(`total out size ${totalOutSize}`); - console.warn(`change ${utxoSize - totalOutSize}`); + console.warn(`change ${Math.floor(Number(utxoSize - totalOutSize)) - 0.0001 + (largestUTXO.interest)}`); const payload = { wif, @@ -111,19 +233,25 @@ class Tools extends React.Component { utxo: [largestUTXO], changeAddress: address, outputAddress: address, - change: Math.floor(Number(utxoSize - totalOutSize) * 100000000) - 10000, // 10k sat fee + change: Math.floor(Number(utxoSize - totalOutSize) * 100000000) - 10000 + (largestUTXO.interest * 100000000), // 10k sat fee }; + console.warn(payload); + console.warn(largestUTXO); + shepherdElectrumSplitUtxoPromise(payload) .then((res) => { console.warn(res); if (res.msg === 'success') { - //this.setState({ - // utxoSplitRawtx: res.result, - //}); + const _coin = this.state.utxoSplitCoin.split('|'); - shepherdCliPromise(null, this.state.utxoSplitCoin, 'sendrawtransaction', [res.result]) + shepherdCliPromise( + null, + _coin[0], + 'sendrawtransaction', + [res.result] + ) .then((res) => { console.warn(res); @@ -131,6 +259,13 @@ class Tools extends React.Component { this.setState({ utxoSplitPushResult: res.result, }); + Store.dispatch( + triggerToaster( + 'Split success', + 'UTXO', + 'success' + ) + ); } else { Store.dispatch( triggerToaster( @@ -154,19 +289,136 @@ class Tools extends React.Component { } getUtxoSplit() { - shepherdCliPromise(null, this.state.utxoSplitCoin, 'listunspent') - .then((res) => { - console.warn(res); + const _coin = this.state.utxoSplitCoin.split('|'); - if (!res.error) { - this.setState({ - utxoSplitList: res.result, + shepherdToolsSeedToWif( + this.state.utxoSplitSeed, + 'KMD', + true + ) + .then((seed2kpRes) => { + if (seed2kpRes.msg === 'success') { + shepherdCliPromise(null, _coin[0], 'listunspent') + .then((res) => { + // console.warn(res); + + if (!res.error) { + const _utxoList = res.result; + let largestUTXO = 0; + + if (_utxoList && + _utxoList.length) { + let _mineUtxo = []; + + for (let i = 0; i < _utxoList.length; i++) { + if (_utxoList[i].spendable && + seed2kpRes.result.keys.pub === _utxoList[i].address) { + _mineUtxo.push(_utxoList[i]); + } + } + + for (let i = 0; i < _mineUtxo.length; i++) { + if (Number(_mineUtxo[i].amount) > Number(largestUTXO)) { + largestUTXO = _mineUtxo[i].amount; + } + } + + this.setState({ + utxoSplitList: _mineUtxo, + utxoSplitLargestUtxo: largestUTXO, + utxoSplitAddress: seed2kpRes.result.keys.pub, + utxoSplitWif: seed2kpRes.result.keys.priv, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Split UTXO error', + 'error' + ) + ); + } + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Get UTXO error', + 'error' + ) + ); + } }); } else { Store.dispatch( triggerToaster( - res.result, - 'Get UTXO error', + seed2kpRes.result, + 'Seed to wif error', + 'error' + ) + ); + } + }); + } + + getUtxoMerge() { + const _coin = this.state.utxoMergeCoin.split('|'); + + shepherdToolsSeedToWif( + this.state.utxoMergeSeed, + 'KMD', + true + ) + .then((seed2kpRes) => { + if (seed2kpRes.msg === 'success') { + shepherdCliPromise(null, _coin[0], 'listunspent') + .then((res) => { + // console.warn(res); + + if (!res.error) { + const _utxoList = res.result; + let largestUTXO = 0; + + if (_utxoList && + _utxoList.length) { + let _mineUtxo = []; + + for (let i = 0; i < _utxoList.length; i++) { + if (_utxoList[i].spendable && + seed2kpRes.result.keys.pub === _utxoList[i].address) { + _mineUtxo.push(_utxoList[i]); + } + } + + this.setState({ + utxoMergeList: _mineUtxo, + utxoMergeAddress: seed2kpRes.result.keys.pub, + utxoMergeWif: seed2kpRes.result.keys.priv, + utxoMergeUtxoNum: _mineUtxo.length, + }); + } else { + Store.dispatch( + triggerToaster( + 'Utxo Merge Error', + 'No valid UTXO', + 'error' + ) + ); + } + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Get UTXO error', + 'error' + ) + ); + } + }); + } else { + Store.dispatch( + triggerToaster( + seed2kpRes.result, + 'Seed to wif error', 'error' ) ); @@ -388,6 +640,29 @@ class Tools extends React.Component { }); } + openExplorerWindow(txid, coin) { + const url = `http://${coin}.explorer.supernet.org/tx/${txid}`; + const remote = window.require('electron').remote; + const BrowserWindow = remote.BrowserWindow; + + const externalWindow = new BrowserWindow({ + width: 1280, + height: 800, + title: `${translate('INDEX.LOADING')}...`, + icon: remote.getCurrentWindow().iguanaIcon, + webPreferences: { + nodeIntegration: false, + }, + }); + + externalWindow.loadURL(url); + externalWindow.webContents.on('did-finish-load', () => { + setTimeout(() => { + externalWindow.show(); + }, 40); + }); + } + renderUTXOResponse() { const _utxos = this.state.utxoResult; const _coin = this.state.utxoCoin.split('|'); @@ -441,10 +716,9 @@ class Tools extends React.Component { ); } - renderUTXOSplitResponse() { - const _utxos = this.state.utxoSplitList; + renderUTXOSplitMergeResponse(type) { + const _utxos = type === 'merge' ? this.state.utxoMergeList : this.state.utxoSplitList; let _items = []; - console.warn(_utxos); if (_utxos && _utxos.length) { @@ -496,52 +770,68 @@ class Tools extends React.Component {

Tools

- - - - - - - - -
* Electrum
+ +
* Electrum, ** Native

{ this.state.activeSection === 'offlinesig-create' &&
-

Offline Transaction Signing

+
+

Offline Transaction Signing

+