diff --git a/react/src/actions/actions/tools.js b/react/src/actions/actions/tools.js index 17d1977..3295601 100644 --- a/react/src/actions/actions/tools.js +++ b/react/src/actions/actions/tools.js @@ -1,8 +1,6 @@ import { translate } from '../../translate/translate'; import Config from '../../config'; -import { - triggerToaster, -} from '../actionCreators'; +import { triggerToaster } from '../actionCreators'; import Store from '../../store'; export function shepherdToolsSeedKeys(seed) { diff --git a/react/src/components/dashboard/tools/tools.js b/react/src/components/dashboard/tools/tools.js index b22e26e..d6d2128 100644 --- a/react/src/components/dashboard/tools/tools.js +++ b/react/src/components/dashboard/tools/tools.js @@ -1,631 +1,22 @@ import React from 'react'; import { translate } from '../../../translate/translate'; -import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; -import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; -import Select from 'react-select'; -import { - triggerToaster, - shepherdToolsBalance, - shepherdToolsBuildUnsigned, - shepherdToolsPushTx, - shepherdToolsSeedToWif, - shepherdToolsWifToKP, - shepherdElectrumListunspent, - shepherdCliPromise, - shepherdElectrumSplitUtxoPromise, -} from '../../../actions/actionCreators'; -import Store from '../../../store'; -import QRCode from 'qrcode.react'; -import QRModal from '../qrModal/qrModal'; -import { isKomodoCoin } from '../../../util/coinHelper'; + +import ToolsGetBalance from './toolsGetBalance'; +import ToolsGetUtxos from './toolsGetUtxos'; +import ToolsOfflineSigCreate from './toolsOfflineSigCreate'; +import ToolsOfflineSigScan from './toolsOfflineSigScan'; +import ToolsSeedToWif from './toolsSeedToWif'; +import ToolsWifToWif from './toolsWifToWif'; +import ToolsStringToQr from './toolsStringToQr'; +import ToolsMergeUTXO from './toolsMergeUtxo'; +import ToolsSplitUTXO from './toolsSplitUtxo'; class Tools extends React.Component { constructor() { super(); this.state = { - sendFrom: '', - sendTo: '', - amount: 0, - selectedCoin: '', - balance: null, - tx2qr: null, - utxo: null, - 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, - // 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); - this.getBalance = this.getBalance.bind(this); - this.getUnsignedTx = this.getUnsignedTx.bind(this); - this.sendTx = this.sendTx.bind(this); - this.closeQr = this.closeQr.bind(this); this.setActiveSection = this.setActiveSection.bind(this); - this.seed2Wif = this.seed2Wif.bind(this); - this.wif2wif = this.wif2wif.bind(this); - 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); - 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() { - 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 = this.state.utxoSplitWif; - const address = this.state.utxoSplitAddress; - const pairsCount = this.state.utxoSplitPairsCount; - let totalOutSize = 0; - let _targets = []; - - 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 < 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 ${Math.floor(Number(utxoSize - totalOutSize)) - 0.0001 + (largestUTXO.interest)}`); - - const payload = { - wif, - network: 'komodo', - targets: _targets, - utxo: [largestUTXO], - changeAddress: address, - outputAddress: address, - 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') { - const _coin = this.state.utxoSplitCoin.split('|'); - - shepherdCliPromise( - null, - _coin[0], - 'sendrawtransaction', - [res.result] - ) - .then((res) => { - console.warn(res); - - if (!res.error) { - this.setState({ - utxoSplitPushResult: res.result, - }); - Store.dispatch( - triggerToaster( - 'Split success', - 'UTXO', - 'success' - ) - ); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Split UTXO error', - 'error' - ) - ); - } - }); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Split UTXO error', - 'error' - ) - ); - } - }); - } - - getUtxoSplit() { - const _coin = this.state.utxoSplitCoin.split('|'); - - 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( - 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' - ) - ); - } - }); - } - - getUtxos() { - const _coin = this.state.utxoCoin.split('|'); - - shepherdElectrumListunspent(_coin[0], this.state.utxoAddr) - .then((res) => { - if (res.msg === 'success') { - this.setState({ - utxoResult: res.result, - }); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Get UTXO error', - 'error' - ) - ); - } - }); - } - - getBalanceAlt() { - const _coin = this.state.balanceCoin.split('|'); - - shepherdToolsBalance(_coin[0], this.state.balanceAddr) - .then((res) => { - if (res.msg === 'success') { - this.setState({ - balanceResult: res.result, - }); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Get balance error', - 'error' - ) - ); - } - }); - } - - wif2wif() { - const _coin = this.state.w2wCoin.split('|'); - - shepherdToolsWifToKP(_coin[0], this.state.w2wWif) - .then((res) => { - // console.warn(res); - - if (res.msg === 'success') { - this.setState({ - w2wResult: res.result, - }); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Seed to wif error', - 'error' - ) - ); - } - }); - } - - seed2Wif() { - const _coin = this.state.s2wCoin.split('|'); - - shepherdToolsSeedToWif( - this.state.s2wSeed, - _coin[0], - this.state.s2wisIguana - ) - .then((res) => { - // console.warn(res); - - if (res.msg === 'success') { - this.setState({ - s2wResult: res.result, - }); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Seed to wif error', - 'error' - ) - ); - } - }); - } - - sendTx(rawTx2Push) { - let _txData = rawTx2Push.split(':'); - - // console.warn(_txData); - - shepherdToolsPushTx(_txData[0], _txData[1]) - .then((res) => { - // console.warn(res); - - this.setState({ - txPushResult: res.result, - rawTx2Push, - }); - }); - } - - getBalance() { - const _coin = this.state.selectedCoin.split('|'); - - shepherdToolsBalance(_coin[0], this.state.sendFrom) - .then((res) => { - if (res.msg === 'success') { - this.setState({ - balance: res.result, - }); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Offline tx signing', - 'error' - ) - ); - } - }); - } - - getUnsignedTx() { - const _coin = this.state.selectedCoin.split('|'); - - shepherdToolsBuildUnsigned(_coin[0], this.state.amount * 100000000, this.state.sendTo, this.state.sendFrom) - .then((res) => { - // console.warn(res); - - if (res.msg === 'success') { - let tx2qr = 'agtx:'; - res = res.result; - - tx2qr += (res.network === 'komodo' ? 'KMD' : res.network) + ':' + res.outputAddress + ':' + res.changeAddress + ':' + res.value + ':' + res.change + ':u:'; - - for (let i = 0; i < res.utxoSet.length; i++) { - tx2qr += res.utxoSet[i].txid + ':' + res.utxoSet[i].value + ':' + res.utxoSet[i].vout + (i === res.utxoSet.length -1 ? '' : '-'); - } - - // console.warn(tx2qr); - // console.warn('txqr length', tx2qr.length); - - // max 350 chars - - this.setState({ - tx2qr, - utxo: res.utxoSet, - }); - } else { - Store.dispatch( - triggerToaster( - res.result, - 'Offline tx signing', - 'error' - ) - ); - } - }); - } - - closeQr() { - this.setState({ - tx2qr: null, - }); - } - - renderCoinOption(option) { - return ( -
- { - { option.label } -
- ); - } - - updateSelectedCoin(e, propName) { - if (e && - e.value && - e.value.indexOf('|')) { - this.setState({ - [propName]: e.value, - }); - } - } - - updateInput(e) { - this.setState({ - [e.target.name]: e.target.value, - }); } setActiveSection(section) { @@ -634,134 +25,6 @@ class Tools extends React.Component { }); } - toggleS2wIsIguana() { - this.setState({ - s2wisIguana: !this.state.s2wisIguana, - }); - } - - 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('|'); - let _items = []; - - if (_utxos && - _utxos.length) { - for (let i = 0; i < _utxos.length; i++) { - _items.push( - - { _utxos[i].amount } - { _utxos[i].confirmations } - { _utxos[i].vout } - { _coin[0] === 'KMD' && - { _utxos[i].locktime } - } - { _utxos[i].txid } - - ); - } - } - - return ( - - - - - - - { _coin[0] === 'KMD' && - - } - - - - - { _items } - - - - - - - { _coin[0] === 'KMD' && - - } - - - -
AmountConfirmationsVoutLocktimeTxID
AmountConfirmationsVoutLocktimeTxID
- ); - } - - renderUTXOSplitMergeResponse(type) { - const _utxos = type === 'merge' ? this.state.utxoMergeList : this.state.utxoSplitList; - let _items = []; - - 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 (
@@ -828,680 +91,31 @@ class Tools extends React.Component {

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

Offline Transaction Signing

-
-
- - -
-
- - { this.state.balance && - - } -
-
- - -
-
- - -
-
- -
- { this.state.tx2qr && -
- - -
- } - { this.state.tx2qr && -
- - { this.state.utxo.length > 3 && -
cant encode a qr tx larger than 3 utxos!
- } -
- } - { this.state.tx2qr && - this.state.utxo.length < 4 && -
-
-
- -
- -
-
- } -
+ } { this.state.activeSection === 'offlinesig-scan' && -
-
-

Push QR transaction

-
-
- -
- { this.state.rawTx2Push && -
- -
- } - { this.state.txPushResult && -
- { this.state.txPushResult.length === 64 && -
-
- { this.state.rawTx2Push.split(':')[0].toUpperCase() } transaction pushed! -
-
TxID { this.state.txPushResult }
-
- } - { this.state.txPushResult.length !== 64 && -
Error: { this.state.txPushResult }
- } -
- } -
+ } { this.state.activeSection === 'string2qr' && -
-
-

String to QR

-
-
- -
- { this.state.string2qr && -
- -
- } -
+ } { this.state.activeSection === 'balance' && -
-
-

Get balance

-
-
- - -
-
- -
- { this.state.balanceResult && -
-
- Balance (confirmed): { this.state.balanceResult.balance } -
-
- Balance (unconfirmed): { this.state.balanceResult.unconfirmed } -
-
- } -
+ } { this.state.activeSection === 'utxo' && -
-
-

Get UTXO list

-
-
- - -
-
- -
- { this.state.utxoResult && -
- { this.renderUTXOResponse() } -
- } -
+ } { this.state.activeSection === 'wif2wif' && -
-
-

WIF to key pair (wif, pub)

-
-
- - -
-
- -
- { this.state.w2wResult && -
-
- WIF: { this.state.w2wResult.keys.priv } -
-
- Pub: { this.state.w2wResult.keys.pub } -
-
- } -
+ } { this.state.activeSection === 'seed2kp' && -
-
-

Seed to key pair (wif, pub)

-
-
- - -
-
- -
- Iguana Core compatible -
-
-
- -
- { this.state.s2wResult && -
-
- WIF: { this.state.s2wResult.keys.priv } -
-
- Pub: { this.state.s2wResult.keys.pub } -
-
- } -
+ } { this.state.activeSection === 'utxo-split' && -
-
-

Split UTXO

-
-
- - -
- { this.state.utxoSplitAddress && -
- Pub: { this.state.utxoSplitAddress } -
- } - { this.state.utxoSplitAddress && -
- WIF: { this.state.utxoSplitWif } -
- } -
- -
- { this.state.utxoSplitList && -
- { /*this.renderUTXOSplitResponse()*/ } -
Total UTXO: { this.state.utxoSplitList.length }
-
Largest UTXO: { this.state.utxoSplitLargestUtxo }
-
- } -
- -
- Show UTXO list -
-
- { this.state.utxoSplitShowUtxoList && -
- { this.renderUTXOSplitMergeResponse('split') } -
- } -
- - -
-
- - -
-
- - -
- { this.state.splitUtxoApproximateVal && -
- Total out size: { this.state.splitUtxoApproximateVal } -
- } - { - /*this.state.utxoSplitRawtx && -
- Rawtx:
{ this.state.utxoSplitRawtx }
-
*/ - } - { this.state.utxoSplitPushResult && -
- TXID:
{ this.state.utxoSplitPushResult }
- { isKomodoCoin(this.state.utxoSplitCoin.split('|')[0]) && -
- -
- } -
- } -
+ } { this.state.activeSection === 'utxo-merge' && -
-
-

Merge UTXO

-
-
- - -
- { this.state.utxoMergeAddress && -
- Pub: { this.state.utxoMergeAddress } -
- } - { this.state.utxoMergeAddress && -
- WIF: { this.state.utxoMergeWif } -
- } -
- -
- { this.state.utxoMergeList && -
-
Total UTXO: { this.state.utxoMergeList.length }
-
- } -
- -
- Show UTXO list -
-
- { this.state.utxoMergeShowUtxoList && -
- { this.renderUTXOSplitMergeResponse('merge') } -
- } -
- - -
-
- -
- { /*this.state.utxoMergeRawtx && -
- Rawtx:
{ this.state.utxoMergeRawtx }
-
*/ - } - { this.state.utxoMergePushResult && -
- TXID:
{ this.state.utxoMergePushResult }
- { isKomodoCoin(this.state.utxoMergeCoin.split('|')[0]) && -
- -
- } -
- } -
+ } diff --git a/react/src/components/dashboard/tools/toolsGetBalance.js b/react/src/components/dashboard/tools/toolsGetBalance.js new file mode 100644 index 0000000..359cc09 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsGetBalance.js @@ -0,0 +1,138 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; + +class ToolsGetBalance extends React.Component { + constructor() { + super(); + this.state = { + balanceAddr: '', + balanceCoin: '', + balanceResult: null, + }; + this.updateInput = this.updateInput.bind(this); + this.updateSelectedCoin = this.updateSelectedCoin.bind(this); + this.getBalanceAlt = this.getBalanceAlt.bind(this); + } + + getBalanceAlt() { + const _coin = this.state.balanceCoin.split('|'); + + shepherdToolsBalance(_coin[0], this.state.balanceAddr) + .then((res) => { + if (res.msg === 'success') { + this.setState({ + balanceResult: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Get balance error', + 'error' + ) + ); + } + }); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
+
+

Get balance

+
+
+ + +
+
+ +
+ { this.state.balanceResult && +
+
+ Balance (confirmed): { this.state.balanceResult.balance } +
+
+ Balance (unconfirmed): { this.state.balanceResult.unconfirmed } +
+
+ } +
+ ); + } +} + +export default ToolsGetBalance; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsGetUtxos.js b/react/src/components/dashboard/tools/toolsGetUtxos.js new file mode 100644 index 0000000..f065815 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsGetUtxos.js @@ -0,0 +1,186 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; + +class ToolsGetUtxos extends React.Component { + constructor() { + super(); + this.state = { + utxoAddr: '', + utxoCoin: '', + utxoResult: null, + }; + this.updateInput = this.updateInput.bind(this); + this.updateSelectedCoin = this.updateSelectedCoin.bind(this); + this.getUtxos = this.getUtxos.bind(this); + } + + getUtxos() { + const _coin = this.state.utxoCoin.split('|'); + + shepherdElectrumListunspent(_coin[0], this.state.utxoAddr) + .then((res) => { + if (res.msg === 'success') { + this.setState({ + utxoResult: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Get UTXO error', + 'error' + ) + ); + } + }); + } + + renderUTXOResponse() { + const _utxos = this.state.utxoResult; + const _coin = this.state.utxoCoin.split('|'); + let _items = []; + + if (_utxos && + _utxos.length) { + for (let i = 0; i < _utxos.length; i++) { + _items.push( + + { _utxos[i].amount } + { _utxos[i].confirmations } + { _utxos[i].vout } + { _coin[0] === 'KMD' && + { _utxos[i].locktime } + } + { _utxos[i].txid } + + ); + } + } + + return ( + + + + + + + { _coin[0] === 'KMD' && + + } + + + + + { _items } + + + + + + + { _coin[0] === 'KMD' && + + } + + + +
AmountConfirmationsVoutLocktimeTxID
AmountConfirmationsVoutLocktimeTxID
+ ); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
+
+

Get UTXO list

+
+
+ + +
+
+ +
+ { this.state.utxoResult && +
+ { this.renderUTXOResponse() } +
+ } +
+ ); + } +} + +export default ToolsGetUtxos; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsMergeUtxo.js b/react/src/components/dashboard/tools/toolsMergeUtxo.js new file mode 100644 index 0000000..92f0c85 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsMergeUtxo.js @@ -0,0 +1,424 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; +import { isKomodoCoin } from '../../../util/coinHelper'; +import devlog from '../../../util/devlog'; + +class ToolsMergeUTXO extends React.Component { + constructor() { + super(); + this.state = { + 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); + this.getUtxoMerge = this.getUtxoMerge.bind(this); + this.mergeUtxo = this.mergeUtxo.bind(this); + this.toggleMergeUtxoList = this.toggleMergeUtxoList.bind(this); + } + + toggleMergeUtxoList() { + this.setState({ + utxoMergeShowUtxoList: !this.state.utxoMergeShowUtxoList, + }); + } + + 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++) { + devlog(`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 ? _utxos[i].interest : 0); + } + + devlog(`total out size ${totalOutSize}`); + devlog(`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) => { + devlog(res); + + if (res.msg === 'success') { + const _coin = this.state.utxoMergeCoin.split('|'); + + shepherdCliPromise( + null, + _coin[0], + 'sendrawtransaction', + [res.result] + ) + .then((res) => { + devlog(res); + + if (!res.error) { + this.setState({ + utxoMergePushResult: res.result, + }); + Store.dispatch( + triggerToaster( + 'Merge success', + 'UTXO', + 'success' + ) + ); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Merge UTXO error', + 'error' + ) + ); + } + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Merge UTXO 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) => { + // devlog(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' + ) + ); + } + }); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + 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); + }); + } + + renderUTXOSplitMergeResponse(type) { + const _utxos = type === 'merge' ? this.state.utxoMergeList : this.state.utxoSplitList; + let _items = []; + + 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 ( +
+
+

Merge UTXO

+
+
+ + +
+ { this.state.utxoMergeAddress && +
+ Pub: { this.state.utxoMergeAddress } +
+ } + { this.state.utxoMergeAddress && +
+ WIF: { this.state.utxoMergeWif } +
+ } +
+ +
+ { this.state.utxoMergeList && +
+
Total UTXO: { this.state.utxoMergeList.length }
+
+ } +
+ +
+ Show UTXO list +
+
+ { this.state.utxoMergeShowUtxoList && +
+ { this.renderUTXOSplitMergeResponse('merge') } +
+ } +
+ + +
+
+ +
+ { /*this.state.utxoMergeRawtx && +
+ Rawtx:
{ this.state.utxoMergeRawtx }
+
*/ + } + { this.state.utxoMergePushResult && +
+ TXID:
{ this.state.utxoMergePushResult }
+ { isKomodoCoin(this.state.utxoMergeCoin.split('|')[0]) && +
+ +
+ } +
+ } +
+ ); + } +} + +export default ToolsMergeUTXO; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsOfflineSigCreate.js b/react/src/components/dashboard/tools/toolsOfflineSigCreate.js new file mode 100644 index 0000000..2796566 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsOfflineSigCreate.js @@ -0,0 +1,263 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; +import QRCode from 'qrcode.react'; +import QRModal from '../qrModal/qrModal'; + +class ToolsOfflineSigCreate extends React.Component { + constructor() { + super(); + this.state = { + sendFrom: '', + sendTo: '', + amount: 0, + selectedCoin: '', + balance: null, + tx2qr: null, + utxo: null, + rawTx2Push: null, + txPushResult: null, + }; + this.updateInput = this.updateInput.bind(this); + this.updateSelectedCoin = this.updateSelectedCoin.bind(this); + this.getBalance = this.getBalance.bind(this); + this.getUnsignedTx = this.getUnsignedTx.bind(this); + this.closeQr = this.closeQr.bind(this); + } + + getBalance() { + const _coin = this.state.selectedCoin.split('|'); + + shepherdToolsBalance(_coin[0], this.state.sendFrom) + .then((res) => { + if (res.msg === 'success') { + this.setState({ + balance: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Offline tx signing', + 'error' + ) + ); + } + }); + } + + getUnsignedTx() { + const _coin = this.state.selectedCoin.split('|'); + + shepherdToolsBuildUnsigned(_coin[0], this.state.amount * 100000000, this.state.sendTo, this.state.sendFrom) + .then((res) => { + // console.warn(res); + + if (res.msg === 'success') { + let tx2qr = 'agtx:'; + res = res.result; + + tx2qr += (res.network === 'komodo' ? 'KMD' : res.network) + ':' + res.outputAddress + ':' + res.changeAddress + ':' + res.value + ':' + res.change + ':u:'; + + for (let i = 0; i < res.utxoSet.length; i++) { + tx2qr += res.utxoSet[i].txid + ':' + res.utxoSet[i].value + ':' + res.utxoSet[i].vout + (i === res.utxoSet.length -1 ? '' : '-'); + } + + // console.warn(tx2qr); + // console.warn('txqr length', tx2qr.length); + + // max 350 chars + + this.setState({ + tx2qr, + utxo: res.utxoSet, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Offline tx signing', + 'error' + ) + ); + } + }); + } + + closeQr() { + this.setState({ + tx2qr: null, + }); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
+
+

Offline Transaction Signing

+
+
+ + +
+
+ + { this.state.balance && + + } +
+
+ + +
+
+ + +
+
+ +
+ { this.state.tx2qr && +
+ + +
+ } + { this.state.tx2qr && +
+ + { this.state.utxo.length > 3 && +
cant encode a qr tx larger than 3 utxos!
+ } +
+ } + { this.state.tx2qr && + this.state.utxo.length < 4 && +
+
+
+ +
+ +
+
+ } +
+ ); + } +} + +export default ToolsOfflineSigCreate; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsOfflineSigScan.js b/react/src/components/dashboard/tools/toolsOfflineSigScan.js new file mode 100644 index 0000000..5e998f8 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsOfflineSigScan.js @@ -0,0 +1,125 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; +import QRCode from 'qrcode.react'; +import QRModal from '../qrModal/qrModal'; + +class ToolsOfflineSigScan extends React.Component { + constructor() { + super(); + this.state = { + sendFrom: '', + sendTo: '', + amount: 0, + selectedCoin: '', + balance: null, + tx2qr: null, + utxo: null, + rawTx2Push: null, + txPushResult: null, + }; + this.updateInput = this.updateInput.bind(this); + this.updateSelectedCoin = this.updateSelectedCoin.bind(this); + this.sendTx = this.sendTx.bind(this); + } + + sendTx(rawTx2Push) { + let _txData = rawTx2Push.split(':'); + + // console.warn(_txData); + + shepherdToolsPushTx(_txData[0], _txData[1]) + .then((res) => { + // console.warn(res); + + this.setState({ + txPushResult: res.result, + rawTx2Push, + }); + }); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
+
+

Push QR transaction

+
+
+ +
+ { this.state.rawTx2Push && +
+ +
+ } + { this.state.txPushResult && +
+ { this.state.txPushResult.length === 64 && +
+
+ { this.state.rawTx2Push.split(':')[0].toUpperCase() } transaction pushed! +
+
TxID { this.state.txPushResult }
+
+ } + { this.state.txPushResult.length !== 64 && +
Error: { this.state.txPushResult }
+ } +
+ } +
+ ); + } +} + +export default ToolsOfflineSigScan; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsSeedToWif.js b/react/src/components/dashboard/tools/toolsSeedToWif.js new file mode 100644 index 0000000..b6ce43b --- /dev/null +++ b/react/src/components/dashboard/tools/toolsSeedToWif.js @@ -0,0 +1,167 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; + +class ToolsSeedToWif extends React.Component { + constructor() { + super(); + this.state = { + s2wSeed: '', + s2wCoin: '', + s2wisIguana: true, + s2wResult: null, + }; + this.updateInput = this.updateInput.bind(this); + this.updateSelectedCoin = this.updateSelectedCoin.bind(this); + this.seed2Wif = this.seed2Wif.bind(this); + this.toggleS2wIsIguana = this.toggleS2wIsIguana.bind(this); + } + + seed2Wif() { + const _coin = this.state.s2wCoin.split('|'); + + shepherdToolsSeedToWif( + this.state.s2wSeed, + _coin[0], + this.state.s2wisIguana + ) + .then((res) => { + // console.warn(res); + + if (res.msg === 'success') { + this.setState({ + s2wResult: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Seed to wif error', + 'error' + ) + ); + } + }); + } + + toggleS2wIsIguana() { + this.setState({ + s2wisIguana: !this.state.s2wisIguana, + }); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
+
+

Seed to key pair (wif, pub)

+
+
+ + +
+
+ +
+ Iguana Core compatible +
+
+
+ +
+ { this.state.s2wResult && +
+
+ WIF: { this.state.s2wResult.keys.priv } +
+
+ Pub: { this.state.s2wResult.keys.pub } +
+
+ } +
+ ); + } +} + +export default ToolsSeedToWif; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsSplitUtxo.js b/react/src/components/dashboard/tools/toolsSplitUtxo.js new file mode 100644 index 0000000..4f77925 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsSplitUtxo.js @@ -0,0 +1,520 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; +import { isKomodoCoin } from '../../../util/coinHelper'; +import devlog from '../../../util/devlog'; + +class ToolsSplitUTXO extends React.Component { + constructor() { + super(); + this.state = { + utxoSplitLargestUtxo: null, + utxoSplitAddress: null, + utxoSplitWif: null, + utxoSplitSeed: '', + utxoSplitCoin: '', + utxoSplitList: null, + utxoSplitPairsCount: 1, + utxoSplitPairs: '10,0.002', + utxoSplitRawtx: null, + utxoSplitPushResult: null, + utxoSplitShowUtxoList: false, + splitUtxoApproximateVal: null, + }; + this.updateInput = this.updateInput.bind(this); + this.updateSelectedCoin = this.updateSelectedCoin.bind(this); + this.getUtxoSplit = this.getUtxoSplit.bind(this); + this.splitUtxo = this.splitUtxo.bind(this); + this.toggleSplitUtxoList = this.toggleSplitUtxoList.bind(this); + this.splitUtxoApproximate = this.splitUtxoApproximate.bind(this); + } + + toggleSplitUtxoList() { + this.setState({ + utxoSplitShowUtxoList: !this.state.utxoSplitShowUtxoList, + }); + } + + splitUtxoApproximate() { + 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 = JSON.parse(JSON.stringify(this.state.utxoSplitList[i])); + } + } + + devlog(`largest utxo ${largestUTXO.amount}`); + devlog(`largest utxo ${largestUTXO.amount}`); + + const utxoSize = largestUTXO.amount; + const targetSizes = this.state.utxoSplitPairs.split(','); + const wif = this.state.utxoSplitWif; + const address = this.state.utxoSplitAddress; + const pairsCount = this.state.utxoSplitPairsCount; + let totalOutSize = 0; + let _targets = []; + + devlog(`total utxos ${pairsCount * targetSizes.length}`); + devlog(`total pairs ${pairsCount}`); + devlog(`utxo size ${utxoSize}`); + devlog(`utxo sizes`); + devlog(targetSizes); + + for (let i = 0; i < pairsCount; i++) { + for (let j = 0; j < targetSizes.length; j++) { + devlog(`vout ${_targets.length} ${targetSizes[j]}`); + _targets.push(Number(targetSizes[j]) * 100000000); + totalOutSize += Number(targetSizes[j]); + } + } + + devlog(`total out size ${totalOutSize}`); + devlog(`largest utxo size ${largestUTXO.amount}`); + devlog(`change ${Number(largestUTXO.amount - totalOutSize) - 0.0001 + (largestUTXO.interest ? largestUTXO.interest : 0)}`); + + this.setState({ + splitUtxoApproximateVal: largestUTXO.amount - totalOutSize > 0 ? totalOutSize : 'no op, output is bigger than utxo size!', + }); + } + + 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 = JSON.parse(JSON.stringify(this.state.utxoSplitList[i])); + } + } + + devlog(`largest utxo ${largestUTXO.amount}`); + devlog(`largest utxo ${largestUTXO.amount}`); + + const utxoSize = largestUTXO.amount; + const targetSizes = this.state.utxoSplitPairs.split(','); + const wif = this.state.utxoSplitWif; + const address = this.state.utxoSplitAddress; + const pairsCount = this.state.utxoSplitPairsCount; + let totalOutSize = 0; + let _targets = []; + + devlog(`total utxos ${pairsCount * targetSizes.length}`); + devlog(`total pairs ${pairsCount}`); + devlog(`utxo size ${utxoSize}`); + devlog(`utxo sizes`); + devlog(targetSizes); + + for (let i = 0; i < pairsCount; i++) { + for (let j = 0; j < targetSizes.length; j++) { + devlog(`vout ${_targets.length} ${targetSizes[j]}`); + _targets.push(Number(targetSizes[j]) * 100000000); + totalOutSize += Number(targetSizes[j]); + } + } + + devlog(`total out size ${totalOutSize}`); + devlog(`largest utxo size ${largestUTXO.amount}`); + devlog(`change ${Number(largestUTXO.amount - totalOutSize) - 0.0001 + (largestUTXO.interest ? largestUTXO.interest : 0)}`); + + const payload = { + wif, + network: 'komodo', + targets: _targets, + utxo: [largestUTXO], + changeAddress: address, + outputAddress: address, + change: Math.floor(Number(largestUTXO.amount - totalOutSize) * 100000000 - 10000 + ((largestUTXO.interest ? largestUTXO.interest : 0) * 100000000)), // 10k sat fee + }; + + devlog(payload); + devlog(largestUTXO); + + shepherdElectrumSplitUtxoPromise(payload) + .then((res) => { + devlog(res); + + if (res.msg === 'success') { + const _coin = this.state.utxoSplitCoin.split('|'); + + shepherdCliPromise( + null, + _coin[0], + 'sendrawtransaction', + [res.result] + ) + .then((res) => { + devlog(res); + + if (!res.error) { + this.setState({ + utxoSplitPushResult: res.result, + }); + Store.dispatch( + triggerToaster( + 'Split success', + 'UTXO', + 'success' + ) + ); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Split UTXO error', + 'error' + ) + ); + } + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Split UTXO error', + 'error' + ) + ); + } + }); + } + + getUtxoSplit() { + const _coin = this.state.utxoSplitCoin.split('|'); + + shepherdToolsSeedToWif( + this.state.utxoSplitSeed, + 'KMD', + true + ) + .then((seed2kpRes) => { + if (seed2kpRes.msg === 'success') { + shepherdCliPromise(null, _coin[0], 'listunspent') + .then((res) => { + // devlog(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( + seed2kpRes.result, + 'Seed to wif error', + 'error' + ) + ); + } + }); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + 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); + }); + } + + renderUTXOSplitMergeResponse(type) { + const _utxos = type === 'merge' ? this.state.utxoMergeList : this.state.utxoSplitList; + let _items = []; + + 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 ( +
+
+

Split UTXO

+
+
+ + +
+ { this.state.utxoSplitAddress && +
+ Pub: { this.state.utxoSplitAddress } +
+ } + { this.state.utxoSplitAddress && +
+ WIF: { this.state.utxoSplitWif } +
+ } +
+ +
+ { this.state.utxoSplitList && +
+ { /*this.renderUTXOSplitResponse()*/ } +
Total UTXO: { this.state.utxoSplitList.length }
+
Largest UTXO: { this.state.utxoSplitLargestUtxo }
+
+ } +
+ +
+ Show UTXO list +
+
+ { this.state.utxoSplitShowUtxoList && +
+ { this.renderUTXOSplitMergeResponse('split') } +
+ } +
+ + +
+
+ + +
+
+ + +
+ { this.state.splitUtxoApproximateVal && +
+ Total out size: { this.state.splitUtxoApproximateVal } +
+ } + { + /*this.state.utxoSplitRawtx && +
+ Rawtx:
{ this.state.utxoSplitRawtx }
+
*/ + } + { this.state.utxoSplitPushResult && +
+ TXID:
{ this.state.utxoSplitPushResult }
+ { isKomodoCoin(this.state.utxoSplitCoin.split('|')[0]) && +
+ +
+ } +
+ } +
+ ); + } +} + +export default ToolsSplitUTXO; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsStringToQr.js b/react/src/components/dashboard/tools/toolsStringToQr.js new file mode 100644 index 0000000..5974467 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsStringToQr.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; +import QRCode from 'qrcode.react'; +import QRModal from '../qrModal/qrModal'; + +class ToolsStringToQr extends React.Component { + constructor() { + super(); + this.state = { + string2qr: '', + }; + this.updateInput = this.updateInput.bind(this); + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
+
+

String to QR

+
+
+ +
+ { this.state.string2qr && +
+ +
+ } +
+ ); + } +} + +export default ToolsStringToQr; \ No newline at end of file diff --git a/react/src/components/dashboard/tools/toolsWifToWif.js b/react/src/components/dashboard/tools/toolsWifToWif.js new file mode 100644 index 0000000..f989bf7 --- /dev/null +++ b/react/src/components/dashboard/tools/toolsWifToWif.js @@ -0,0 +1,140 @@ +import React from 'react'; +import { translate } from '../../../translate/translate'; +import addCoinOptionsCrypto from '../../addcoin/addcoinOptionsCrypto'; +import addCoinOptionsAC from '../../addcoin/addcoinOptionsAC'; +import Select from 'react-select'; +import { + triggerToaster, + shepherdToolsBalance, + shepherdToolsBuildUnsigned, + shepherdToolsPushTx, + shepherdToolsSeedToWif, + shepherdToolsWifToKP, + shepherdElectrumListunspent, + shepherdCliPromise, + shepherdElectrumSplitUtxoPromise, +} from '../../../actions/actionCreators'; +import Store from '../../../store'; + +class ToolsWifToWif extends React.Component { + constructor() { + super(); + this.state = { + w2wWif: '', + w2wCoin: '', + w2wResult: null, + }; + this.updateInput = this.updateInput.bind(this); + this.updateSelectedCoin = this.updateSelectedCoin.bind(this); + this.wif2wif = this.wif2wif.bind(this); + } + + wif2wif() { + const _coin = this.state.w2wCoin.split('|'); + + shepherdToolsWifToKP(_coin[0], this.state.w2wWif) + .then((res) => { + // console.warn(res); + + if (res.msg === 'success') { + this.setState({ + w2wResult: res.result, + }); + } else { + Store.dispatch( + triggerToaster( + res.result, + 'Seed to wif error', + 'error' + ) + ); + } + }); + } + + renderCoinOption(option) { + return ( +
+ { + { option.label } +
+ ); + } + + updateSelectedCoin(e, propName) { + if (e && + e.value && + e.value.indexOf('|')) { + this.setState({ + [propName]: e.value, + }); + } + } + + updateInput(e) { + this.setState({ + [e.target.name]: e.target.value, + }); + } + + render() { + return ( +
+
+

WIF to key pair (wif, pub)

+
+
+ + +
+
+ +
+ { this.state.w2wResult && +
+
+ WIF: { this.state.w2wResult.keys.priv } +
+
+ Pub: { this.state.w2wResult.keys.pub } +
+
+ } +
+ ); + } +} + +export default ToolsWifToWif; \ No newline at end of file diff --git a/react/src/util/devlog.js b/react/src/util/devlog.js new file mode 100644 index 0000000..8d6857a --- /dev/null +++ b/react/src/util/devlog.js @@ -0,0 +1,9 @@ +const devlog = (msg) => { + const mainWindow = window.require('electron').remote.getCurrentWindow(); + + if (mainWindow.appConfig.dev) { + console.warn(msg); + } +} + +export default devlog; \ No newline at end of file