Browse Source

Merge pull request #18 from SuperNETorg/v0.25

V0.25
v0.25
pbca26 7 years ago
committed by GitHub
parent
commit
37dcdf6319
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      react/src/actions/actionCreators.js
  2. 8
      react/src/actions/actions/cli.js
  3. 32
      react/src/actions/actions/electrum.js
  4. 32
      react/src/actions/actions/fiatRates.js
  5. 57
      react/src/actions/actions/prices.js
  6. 4
      react/src/actions/actions/tools.js
  7. 2
      react/src/actions/storeType.js
  8. 741
      react/src/components/dashboard/tools/tools.js
  9. 138
      react/src/components/dashboard/tools/toolsGetBalance.js
  10. 186
      react/src/components/dashboard/tools/toolsGetUtxos.js
  11. 424
      react/src/components/dashboard/tools/toolsMergeUtxo.js
  12. 263
      react/src/components/dashboard/tools/toolsOfflineSigCreate.js
  13. 125
      react/src/components/dashboard/tools/toolsOfflineSigScan.js
  14. 167
      react/src/components/dashboard/tools/toolsSeedToWif.js
  15. 520
      react/src/components/dashboard/tools/toolsSplitUtxo.js
  16. 61
      react/src/components/dashboard/tools/toolsStringToQr.js
  17. 140
      react/src/components/dashboard/tools/toolsWifToWif.js
  18. 54
      react/src/components/dashboard/walletsBalance/walletsBalance.js
  19. 24
      react/src/components/dashboard/walletsBalance/walletsBalance.render.js
  20. 2
      react/src/components/dashboard/walletsBalance/walletsBalance.scss
  21. 63
      react/src/components/dashboard/walletsMain/walletsMain.js
  22. 5
      react/src/components/login/login.js
  23. 4
      react/src/components/overrides.scss
  24. 1
      react/src/components/toaster/toaster-item.js
  25. 7
      react/src/reducers/dashboard.js
  26. 1
      react/src/styles/index.scss
  27. 9
      react/src/util/devlog.js

1
react/src/actions/actionCreators.js

@ -51,6 +51,7 @@ export * from './actions/electrum';
export * from './actions/mm';
export * from './actions/nativeNetwork';
export * from './actions/tools';
export * from './actions/prices';
export function changeActiveAddress(address) {
return {

8
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',

32
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);
});
});
}

32
react/src/actions/actions/fiatRates.js

@ -1,32 +0,0 @@
import { FIAT_RATES } from '../storeType';
import { triggerToaster } from '../actionCreators';
import Config from '../../config';
export function fiatRates() {
return dispatch => {
return fetch(`http://46.20.235.46:8111/api/rates/kmd`, {
method: 'GET',
})
.catch((error) => {
console.log(error);
dispatch(
triggerToaster(
'fiatRates',
'Error',
'error'
)
);
})
.then(response => response.json())
.then(json => {
dispatch(fiatRates(json));
});
}
}
function fiatRates(json) {
return {
type: FIAT_RATES,
response: json,
}
}

57
react/src/actions/actions/prices.js

@ -0,0 +1,57 @@
import { PRICES } from '../storeType';
import { triggerToaster } from '../actionCreators';
import Config from '../../config';
function fiatRates(pricesJson) {
return dispatch => {
return fetch(`http://atomicexplorer.com/api/rates/kmd`, {
method: 'GET',
})
.catch((error) => {
console.log(error);
dispatch(
triggerToaster(
'fiatRates',
'Error',
'error'
)
);
})
.then(response => response.json())
.then(json => {
let _coins = pricesJson.result;
_coins.fiat = json.result;
dispatch(pricesState(_coins));
});
}
}
export function prices() {
return dispatch => {
return fetch(`http://atomicexplorer.com/api/mm/prices`, {
method: 'GET',
})
.catch((error) => {
console.log(error);
dispatch(
triggerToaster(
'prices',
'Error',
'error'
)
);
})
.then(response => response.json())
.then(json => {
dispatch(fiatRates(json));
});
}
}
function pricesState(json) {
return {
type: PRICES,
prices: json,
}
}

4
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) {

2
react/src/actions/storeType.js

@ -50,7 +50,7 @@ export const DISPLAY_ZCASH_PARAMS_FETCH = 'DISPLAY_ZCASH_PARAMS_FETCH';
export const DASHBOARD_REMOVE_COIN = 'DASHBOARD_REMOVE_COIN';
export const DASHBOARD_ACTIVE_COIN_NET_PEERS = 'DASHBOARD_ACTIVE_COIN_NET_PEERS';
export const DASHBOARD_ACTIVE_COIN_NET_TOTALS = 'DASHBOARD_ACTIVE_COIN_NET_TOTALS';
export const FIAT_RATES = 'FIAT_RATES';
export const PRICES = 'PRICES';
/* dex */
export const DEX_LOGIN = 'DEX_LOGIN';

741
react/src/components/dashboard/tools/tools.js

@ -1,263 +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,
} from '../../../actions/actionCreators';
import Store from '../../../store';
import QRCode from 'qrcode.react';
import QRModal from '../qrModal/qrModal';
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,
s2wSeed: '',
s2wCoin: '',
s2wisIguana: true,
s2wResult: null,
w2wWif: '',
w2wCoin: '',
w2wResult: null,
utxoAddr: '',
utxoCoin: '',
utxoResult: null,
balanceAddr: '',
balanceCoin: '',
balanceResult: 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.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);
}
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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) {
@ -266,65 +25,6 @@ class Tools extends React.Component {
});
}
toggleS2wIsIguana() {
this.setState({
s2wisIguana: !this.state.s2wisIguana,
});
}
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(
<tr key={ `tools-utxos-${i}` }>
<td>{ _utxos[i].amount }</td>
<td>{ _utxos[i].confirmations }</td>
<td>{ _utxos[i].vout }</td>
{ _coin[0] === 'KMD' &&
<td>{ _utxos[i].locktime }</td>
}
<td>{ _utxos[i].txid }</td>
</tr>
);
}
}
return (
<table className="table table-hover dataTable table-striped">
<thead>
<tr>
<th>Amount</th>
<th>Confirmations</th>
<th>Vout</th>
{ _coin[0] === 'KMD' &&
<th>Locktime</th>
}
<th>TxID</th>
</tr>
</thead>
<tbody>
{ _items }
</tbody>
<tfoot>
<tr>
<th>Amount</th>
<th>Confirmations</th>
<th>Vout</th>
{ _coin[0] === 'KMD' &&
<th>Locktime</th>
}
<th>TxID</th>
</tr>
</tfoot>
</table>
);
}
render() {
return (
<div className="page margin-left-0">
@ -333,430 +33,89 @@ class Tools extends React.Component {
<div className="col-sm-12 no-padding-left">
<h2>Tools</h2>
<div className="margin-top-20">
<button type="button"
<button
type="button"
className="btn btn-default"
onClick={ () => this.setActiveSection('offlinesig-create') }>
Offline signing create
</button>
<button type="button"
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('offlinesig-scan') }>
Offline signing scan
</button>
<button type="button"
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('string2qr') }>
String to QR
</button>
<button type="button"
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('seed2kp') }>
Seed to key pair
</button>
<button type="button"
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('wif2wif') }>
WIF to WIF
</button>
<button type="button"
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('balance') }>
Balance *
</button>
<button type="button"
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('utxo') }>
UTXO *
</button>
<div className="margin-top-10">* Electrum</div>
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('utxo-split') }>
Split UTXO **
</button>
<button
type="button"
className="btn btn-default margin-left-20"
onClick={ () => this.setActiveSection('utxo-merge') }>
Merge UTXO **
</button>
<div className="margin-top-10">* Electrum, ** Native</div>
</div>
<hr />
{ this.state.activeSection === 'offlinesig-create' &&
<div className="row margin-left-10">
<h4 className="margin-top-20">Offline Transaction Signing</h4>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="selectedCoin"
className="col-sm-3"
value={ this.state.selectedCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'selectedCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">{ translate('INDEX.SEND_FROM') }</label>
<input
type="text"
className="form-control col-sm-3"
name="sendFrom"
onChange={ this.updateInput }
value={ this.state.sendFrom }
id="kmdWalletSendTo"
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getBalance }>
Get balance
</button>
{ this.state.balance &&
<label className="margin-left-20">Balance: { this.state.balance.balance } </label>
}
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">{ translate('INDEX.SEND_TO') }</label>
<input
type="text"
className="form-control col-sm-3"
name="sendTo"
onChange={ this.updateInput }
value={ this.state.sendTo }
id="kmdWalletSendTo"
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletAmount">
{ translate('INDEX.AMOUNT') }
</label>
<input
type="text"
className="form-control col-sm-3"
name="amount"
value={ this.state.amount }
onChange={ this.updateInput }
id="kmdWalletAmount"
placeholder="0.000"
autoComplete="off" />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<button
type="button"
className="btn btn-primary col-sm-2"
onClick={ this.getUnsignedTx }>
Generate unsigned tx QR
</button>
</div>
{ this.state.tx2qr &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<label className="control-label col-sm-1 no-padding-left">QR payload</label>
<textarea
rows="5"
cols="20"
className="col-sm-7"
value={ this.state.tx2qr }></textarea>
</div>
}
{ this.state.tx2qr &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<label className="control-label col-sm-2 no-padding-left">
UTXO count: { this.state.utxo.length }
</label>
{ this.state.utxo.length > 3 &&
<div className="col-red margin-left-20 margin-top-5">cant encode a qr tx larger than 3 utxos!</div>
}
</div>
}
{ this.state.tx2qr &&
this.state.utxo.length < 4 &&
<div className="offlinesig-qr">
<div className="margin-top-50 margin-bottom-70 center">
<div>
<QRCode
value={ this.state.tx2qr }
size={ 560 } />
</div>
<button
type="button"
className="btn btn-primary col-sm-2"
onClick={ this.closeQr }>
Close
</button>
</div>
</div>
}
</div>
<ToolsOfflineSigCreate />
}
{ this.state.activeSection === 'offlinesig-scan' &&
<div className="row margin-left-10">
<div className="col-sm-12 form-group form-material no-padding-left">
<h4 className="margin-top-20 no-padding-left">Push QR transaction</h4>
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<QRModal
mode="scan"
setRecieverFromScan={ this.sendTx } />
</div>
{ this.state.rawTx2Push &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<textarea
rows="5"
cols="20"
className="col-sm-7 no-padding-left"
value={ this.state.rawTx2Push }></textarea>
</div>
}
{ this.state.txPushResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
{ this.state.txPushResult.length === 64 &&
<div>
<div className="margin-bottom-15">
{ this.state.rawTx2Push.split(':')[0].toUpperCase() } transaction pushed!
</div>
<div>TxID { this.state.txPushResult }</div>
</div>
}
{ this.state.txPushResult.length !== 64 &&
<div>Error: { this.state.txPushResult }</div>
}
</div>
}
</div>
<ToolsOfflineSigScan />
}
{ this.state.activeSection === 'string2qr' &&
<div className="row margin-left-10">
<div className="col-sm-12 form-group form-material no-padding-left">
<h4 className="margin-top-20 no-padding-left">String to QR</h4>
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<input
type="text"
className="form-control col-sm-5"
name="string2qr"
value={ this.state.string2qr }
onChange={ this.updateInput }
placeholder="Type a string here"
autoComplete="off" />
</div>
{ this.state.string2qr &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-50 center">
<QRCode
value={ this.state.string2qr }
size={ 320 } />
</div>
}
</div>
<ToolsStringToQr />
}
{ this.state.activeSection === 'balance' &&
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="balanceCoin"
className="col-sm-3"
value={ this.state.balanceCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'balanceCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Address</label>
<input
type="text"
className="form-control col-sm-3"
name="balanceAddr"
onChange={ this.updateInput }
value={ this.state.balanceAddr }
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getBalanceAlt }>
Get balance
</button>
</div>
{ this.state.balanceResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<div>
<strong>Balance (confirmed):</strong> { this.state.balanceResult.balance }
</div>
<div className="margin-top-10">
<strong>Balance (unconfirmed):</strong> { this.state.balanceResult.unconfirmed }
</div>
</div>
}
</div>
<ToolsGetBalance />
}
{ this.state.activeSection === 'utxo' &&
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="utxoCoin"
className="col-sm-3"
value={ this.state.utxoCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'utxoCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Address</label>
<input
type="text"
className="form-control col-sm-3"
name="utxoAddr"
onChange={ this.updateInput }
value={ this.state.utxoAddr }
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getUtxos }>
Get UTXO(s)
</button>
</div>
{ this.state.utxoResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
{ this.renderUTXOResponse() }
</div>
}
</div>
<ToolsGetUtxos />
}
{ this.state.activeSection === 'wif2wif' &&
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="w2wCoin"
className="col-sm-3"
value={ this.state.w2wCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'w2wCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">WIF</label>
<input
type="text"
className="form-control col-sm-3"
name="w2wWif"
onChange={ this.updateInput }
value={ this.state.w2wWif }
placeholder="Enter a WIF"
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.wif2wif }>
Get WIF
</button>
</div>
{ this.state.w2wResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<div>
<strong>WIF:</strong> { this.state.w2wResult.keys.priv }
</div>
<div className="margin-top-10">
<strong>Pub:</strong> { this.state.w2wResult.keys.pub }
</div>
</div>
}
</div>
<ToolsWifToWif />
}
{ this.state.activeSection === 'seed2kp' &&
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="s2wCoin"
className="col-sm-3"
value={ this.state.s2wCoin }
onChange={ (event) => this.updateSelectedCoin(event, 's2wCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Passphrase</label>
<input
type="text"
className="form-control col-sm-3"
name="s2wSeed"
onChange={ this.updateInput }
value={ this.state.s2wSeed }
placeholder="Enter a passphrase"
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label className="switch">
<input
type="checkbox"
checked={ this.state.s2wisIguana } />
<div
className="slider"
onClick={ this.toggleS2wIsIguana }></div>
</label>
<div
className="toggle-label pointer iguana-core-toggle"
onClick={ this.toggleS2wIsIguana }>
Iguana Core compatible
</div>
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.seed2Wif }>
Get WIF
</button>
</div>
{ this.state.s2wResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<div>
<strong>WIF:</strong> { this.state.s2wResult.keys.priv }
</div>
<div className="margin-top-10">
<strong>Pub:</strong> { this.state.s2wResult.keys.pub }
</div>
</div>
}
</div>
<ToolsSeedToWif />
}
{ this.state.activeSection === 'utxo-split' &&
<ToolsSplitUTXO />
}
{ this.state.activeSection === 'utxo-merge' &&
<ToolsMergeUTXO />
}
</div>
</div>

138
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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 (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>Get balance</h4>
</div>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="balanceCoin"
className="col-sm-3"
value={ this.state.balanceCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'balanceCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Address</label>
<input
type="text"
className="form-control col-sm-3"
name="balanceAddr"
onChange={ this.updateInput }
value={ this.state.balanceAddr }
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getBalanceAlt }>
Get balance
</button>
</div>
{ this.state.balanceResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<div>
<strong>Balance (confirmed):</strong> { this.state.balanceResult.balance }
</div>
<div className="margin-top-10">
<strong>Balance (unconfirmed):</strong> { this.state.balanceResult.unconfirmed }
</div>
</div>
}
</div>
);
}
}
export default ToolsGetBalance;

186
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(
<tr key={ `tools-utxos-${i}` }>
<td>{ _utxos[i].amount }</td>
<td>{ _utxos[i].confirmations }</td>
<td>{ _utxos[i].vout }</td>
{ _coin[0] === 'KMD' &&
<td>{ _utxos[i].locktime }</td>
}
<td>{ _utxos[i].txid }</td>
</tr>
);
}
}
return (
<table className="table table-hover dataTable table-striped">
<thead>
<tr>
<th>Amount</th>
<th>Confirmations</th>
<th>Vout</th>
{ _coin[0] === 'KMD' &&
<th>Locktime</th>
}
<th>TxID</th>
</tr>
</thead>
<tbody>
{ _items }
</tbody>
<tfoot>
<tr>
<th>Amount</th>
<th>Confirmations</th>
<th>Vout</th>
{ _coin[0] === 'KMD' &&
<th>Locktime</th>
}
<th>TxID</th>
</tr>
</tfoot>
</table>
);
}
renderCoinOption(option) {
return (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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 (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>Get UTXO list</h4>
</div>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="utxoCoin"
className="col-sm-3"
value={ this.state.utxoCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'utxoCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Address</label>
<input
type="text"
className="form-control col-sm-3"
name="utxoAddr"
onChange={ this.updateInput }
value={ this.state.utxoAddr }
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getUtxos }>
Get UTXO(s)
</button>
</div>
{ this.state.utxoResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
{ this.renderUTXOResponse() }
</div>
}
</div>
);
}
}
export default ToolsGetUtxos;

424
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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(
<tr key={ `tools-utxos-${i}` }>
<td>{ _utxos[i].amount }</td>
<td>{ _utxos[i].address }</td>
<td>{ _utxos[i].confirmations }</td>
<td>{ _utxos[i].vout }</td>
<td>{ _utxos[i].txid }</td>
</tr>
);
}
}
return (
<table className="table table-hover dataTable table-striped">
<thead>
<tr>
<th>Amount</th>
<th>Address</th>
<th>Confirmations</th>
<th>Vout</th>
<th>TxID</th>
</tr>
</thead>
<tbody>
{ _items }
</tbody>
<tfoot>
<tr>
<th>Amount</th>
<th>Address</th>
<th>Confirmations</th>
<th>Vout</th>
<th>TxID</th>
</tr>
</tfoot>
</table>
);
}
render() {
return (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>Merge UTXO</h4>
</div>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-50">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="utxoMergeCoin"
className="col-sm-3"
value={ this.state.utxoMergeCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'utxoMergeCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ [{
label: 'Komodo (KMD)',
icon: 'KMD',
value: `KMD|native`,
}].concat(addCoinOptionsAC())
} />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Seed</label>
<input
type="text"
className="form-control col-sm-3"
name="utxoMergeSeed"
onChange={ this.updateInput }
value={ this.state.utxoMergeSeed }
placeholder="Enter a seed"
autoComplete="off"
required />
</div>
{ this.state.utxoMergeAddress &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
Pub: { this.state.utxoMergeAddress }
</div>
}
{ this.state.utxoMergeAddress &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
WIF: { this.state.utxoMergeWif }
</div>
}
<div className="col-sm-12 form-group no-padding-left margin-top-20 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getUtxoMerge }>
Get UTXO(s)
</button>
</div>
{ this.state.utxoMergeList &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<div>Total UTXO: { this.state.utxoMergeList.length }</div>
</div>
}
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<label className="switch">
<input
type="checkbox"
checked={ this.state.utxoMergeShowUtxoList } />
<div
className="slider"
onClick={ this.toggleMergeUtxoList }></div>
</label>
<div
className="toggle-label margin-right-15 pointer iguana-core-toggle"
onClick={ this.toggleMergeUtxoList }>
Show UTXO list
</div>
</div>
{ this.state.utxoMergeShowUtxoList &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
{ this.renderUTXOSplitMergeResponse('merge') }
</div>
}
<div className="col-sm-12 form-group form-material no-padding-left padding-top-20 padding-bottom-20">
<label
className="control-label col-sm-2 no-padding-left"
htmlFor="kmdWalletSendTo">UTXO count to merge in 1 tx</label>
<input
type="text"
className="form-control col-sm-3"
name="utxoMergeUtxoNum"
onChange={ this.updateInput }
value={ this.state.utxoMergeUtxoNum }
placeholder="UTXO count"
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.mergeUtxo }>
Merge UTXO(s)
</button>
</div>
{ /*this.state.utxoMergeRawtx &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
Rawtx: <div style={{ wordBreak: 'break-all' }}>{ this.state.utxoMergeRawtx }</div>
</div>*/
}
{ this.state.utxoMergePushResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
TXID: <div style={{ wordBreak: 'break-all' }}>{ this.state.utxoMergePushResult }</div>
{ isKomodoCoin(this.state.utxoMergeCoin.split('|')[0]) &&
<div className="margin-top-10">
<button
type="button"
className="btn btn-sm white btn-dark waves-effect waves-light pull-left"
onClick={ () => this.openExplorerWindow(this.state.utxoMergePushResult, this.state.utxoMergeCoin.split('|')[0]) }>
<i className="icon fa-external-link"></i> { translate('INDEX.OPEN_TRANSACTION_IN_EPLORER', this.state.utxoMergeCoin.split('|')[0]) }
</button>
</div>
}
</div>
}
</div>
);
}
}
export default ToolsMergeUTXO;

263
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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 (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>Offline Transaction Signing</h4>
</div>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="selectedCoin"
className="col-sm-3"
value={ this.state.selectedCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'selectedCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">{ translate('INDEX.SEND_FROM') }</label>
<input
type="text"
className="form-control col-sm-3"
name="sendFrom"
onChange={ this.updateInput }
value={ this.state.sendFrom }
id="kmdWalletSendTo"
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getBalance }>
Get balance
</button>
{ this.state.balance &&
<label className="margin-left-20">Balance: { this.state.balance.balance } </label>
}
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">{ translate('INDEX.SEND_TO') }</label>
<input
type="text"
className="form-control col-sm-3"
name="sendTo"
onChange={ this.updateInput }
value={ this.state.sendTo }
id="kmdWalletSendTo"
placeholder={ translate('SEND.ENTER_ADDRESS') }
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletAmount">
{ translate('INDEX.AMOUNT') }
</label>
<input
type="text"
className="form-control col-sm-3"
name="amount"
value={ this.state.amount }
onChange={ this.updateInput }
id="kmdWalletAmount"
placeholder="0.000"
autoComplete="off" />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<button
type="button"
className="btn btn-primary col-sm-2"
onClick={ this.getUnsignedTx }>
Generate unsigned tx QR
</button>
</div>
{ this.state.tx2qr &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<label className="control-label col-sm-1 no-padding-left">QR payload</label>
<textarea
rows="5"
cols="20"
className="col-sm-7"
value={ this.state.tx2qr }></textarea>
</div>
}
{ this.state.tx2qr &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<label className="control-label col-sm-2 no-padding-left">
UTXO count: { this.state.utxo.length }
</label>
{ this.state.utxo.length > 3 &&
<div className="col-red margin-left-20 margin-top-5">cant encode a qr tx larger than 3 utxos!</div>
}
</div>
}
{ this.state.tx2qr &&
this.state.utxo.length < 4 &&
<div className="offlinesig-qr">
<div className="margin-top-50 margin-bottom-70 center">
<div>
<QRCode
value={ this.state.tx2qr }
size={ 560 } />
</div>
<button
type="button"
className="btn btn-primary col-sm-2"
onClick={ this.closeQr }>
Close
</button>
</div>
</div>
}
</div>
);
}
}
export default ToolsOfflineSigCreate;

125
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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 (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>Push QR transaction</h4>
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<QRModal
mode="scan"
setRecieverFromScan={ this.sendTx } />
</div>
{ this.state.rawTx2Push &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
<textarea
rows="5"
cols="20"
className="col-sm-7 no-padding-left"
value={ this.state.rawTx2Push }></textarea>
</div>
}
{ this.state.txPushResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20">
{ this.state.txPushResult.length === 64 &&
<div>
<div className="margin-bottom-15">
{ this.state.rawTx2Push.split(':')[0].toUpperCase() } transaction pushed!
</div>
<div>TxID { this.state.txPushResult }</div>
</div>
}
{ this.state.txPushResult.length !== 64 &&
<div>Error: { this.state.txPushResult }</div>
}
</div>
}
</div>
);
}
}
export default ToolsOfflineSigScan;

167
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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 (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>Seed to key pair (wif, pub)</h4>
</div>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="s2wCoin"
className="col-sm-3"
value={ this.state.s2wCoin }
onChange={ (event) => this.updateSelectedCoin(event, 's2wCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Passphrase</label>
<input
type="text"
className="form-control col-sm-3"
name="s2wSeed"
onChange={ this.updateInput }
value={ this.state.s2wSeed }
placeholder="Enter a passphrase"
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label className="switch">
<input
type="checkbox"
checked={ this.state.s2wisIguana } />
<div
className="slider"
onClick={ this.toggleS2wIsIguana }></div>
</label>
<div
className="toggle-label pointer iguana-core-toggle"
onClick={ this.toggleS2wIsIguana }>
Iguana Core compatible
</div>
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.seed2Wif }>
Get WIF
</button>
</div>
{ this.state.s2wResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<div>
<strong>WIF:</strong> { this.state.s2wResult.keys.priv }
</div>
<div className="margin-top-10">
<strong>Pub:</strong> { this.state.s2wResult.keys.pub }
</div>
</div>
}
</div>
);
}
}
export default ToolsSeedToWif;

520
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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(
<tr key={ `tools-utxos-${i}` }>
<td>{ _utxos[i].amount }</td>
<td>{ _utxos[i].address }</td>
<td>{ _utxos[i].confirmations }</td>
<td>{ _utxos[i].vout }</td>
<td>{ _utxos[i].txid }</td>
</tr>
);
}
}
return (
<table className="table table-hover dataTable table-striped">
<thead>
<tr>
<th>Amount</th>
<th>Address</th>
<th>Confirmations</th>
<th>Vout</th>
<th>TxID</th>
</tr>
</thead>
<tbody>
{ _items }
</tbody>
<tfoot>
<tr>
<th>Amount</th>
<th>Address</th>
<th>Confirmations</th>
<th>Vout</th>
<th>TxID</th>
</tr>
</tfoot>
</table>
);
}
render() {
return (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>Split UTXO</h4>
</div>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-50">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="utxoSplitCoin"
className="col-sm-3"
value={ this.state.utxoSplitCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'utxoSplitCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ [{
label: 'Komodo (KMD)',
icon: 'KMD',
value: `KMD|native`,
}].concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Seed</label>
<input
type="text"
className="form-control col-sm-3"
name="utxoSplitSeed"
onChange={ this.updateInput }
value={ this.state.utxoSplitSeed }
placeholder="Enter a seed"
autoComplete="off"
required />
</div>
{ this.state.utxoSplitAddress &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
Pub: { this.state.utxoSplitAddress }
</div>
}
{ this.state.utxoSplitAddress &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
WIF: { this.state.utxoSplitWif }
</div>
}
<div className="col-sm-12 form-group no-padding-left margin-top-20 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.getUtxoSplit }>
Get UTXO(s)
</button>
</div>
{ this.state.utxoSplitList &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
{ /*this.renderUTXOSplitResponse()*/ }
<div>Total UTXO: { this.state.utxoSplitList.length }</div>
<div>Largest UTXO: { this.state.utxoSplitLargestUtxo }</div>
</div>
}
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<label className="switch">
<input
type="checkbox"
checked={ this.state.utxoSplitShowUtxoList } />
<div
className="slider"
onClick={ this.toggleSplitUtxoList }></div>
</label>
<div
className="toggle-label margin-right-15 pointer iguana-core-toggle"
onClick={ this.toggleSplitUtxoList }>
Show UTXO list
</div>
</div>
{ this.state.utxoSplitShowUtxoList &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
{ this.renderUTXOSplitMergeResponse('split') }
</div>
}
<div className="col-sm-12 form-group form-material no-padding-left margin-top-20 padding-bottom-20">
<label
className="control-label col-sm-2 no-padding-left"
htmlFor="kmdWalletSendTo">UTXO sizes</label>
<input
type="text"
className="form-control col-sm-3"
name="utxoSplitPairs"
onChange={ this.updateInput }
value={ this.state.utxoSplitPairs }
placeholder="UTXO sized"
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left padding-top-20 padding-bottom-20">
<label
className="control-label col-sm-2 no-padding-left"
htmlFor="kmdWalletSendTo">Number of pairs</label>
<input
type="text"
className="form-control col-sm-3"
name="utxoSplitPairsCount"
onChange={ this.updateInput }
value={ this.state.utxoSplitPairsCount }
placeholder="Pairs"
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.splitUtxoApproximate }>
Calc total output size
</button>
<button
type="button"
className="btn btn-info col-sm-2 margin-left-40"
onClick={ this.splitUtxo }>
Split UTXO(s)
</button>
</div>
{ this.state.splitUtxoApproximateVal &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
Total out size: { this.state.splitUtxoApproximateVal }
</div>
}
{
/*this.state.utxoSplitRawtx &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
Rawtx: <div style={{ wordBreak: 'break-all' }}>{ this.state.utxoSplitRawtx }</div>
</div>*/
}
{ this.state.utxoSplitPushResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
TXID: <div style={{ wordBreak: 'break-all' }}>{ this.state.utxoSplitPushResult }</div>
{ isKomodoCoin(this.state.utxoSplitCoin.split('|')[0]) &&
<div className="margin-top-10">
<button
type="button"
className="btn btn-sm white btn-dark waves-effect waves-light pull-left"
onClick={ () => this.openExplorerWindow(this.state.utxoSplitPushResult, this.state.utxoSplitCoin.split('|')[0]) }>
<i className="icon fa-external-link"></i> { translate('INDEX.OPEN_TRANSACTION_IN_EPLORER', this.state.utxoSplitCoin.split('|')[0]) }
</button>
</div>
}
</div>
}
</div>
);
}
}
export default ToolsSplitUTXO;

61
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 (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>String to QR</h4>
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<input
type="text"
className="form-control col-sm-5"
name="string2qr"
value={ this.state.string2qr }
onChange={ this.updateInput }
placeholder="Type a string here"
autoComplete="off" />
</div>
{ this.state.string2qr &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-50 center">
<QRCode
value={ this.state.string2qr }
size={ 320 } />
</div>
}
</div>
);
}
}
export default ToolsStringToQr;

140
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 (
<div>
<img
src={ `assets/images/cryptologo/${option.icon.toLowerCase()}.png` }
alt={ option.label }
width="30px"
height="30px" />
<span className="margin-left-10">{ option.label }</span>
</div>
);
}
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 (
<div className="row margin-left-10">
<div className="col-xlg-12 form-group form-material no-padding-left padding-bottom-10">
<h4>WIF to key pair (wif, pub)</h4>
</div>
<div className="col-xlg-12 form-group form-material no-padding-left padding-top-20 padding-bottom-70">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">Coin</label>
<Select
name="w2wCoin"
className="col-sm-3"
value={ this.state.w2wCoin }
onChange={ (event) => this.updateSelectedCoin(event, 'w2wCoin') }
optionRenderer={ this.renderCoinOption }
valueRenderer={ this.renderCoinOption }
options={ addCoinOptionsCrypto().concat(addCoinOptionsAC()) } />
</div>
<div className="col-sm-12 form-group form-material no-padding-left">
<label
className="control-label col-sm-1 no-padding-left"
htmlFor="kmdWalletSendTo">WIF</label>
<input
type="text"
className="form-control col-sm-3"
name="w2wWif"
onChange={ this.updateInput }
value={ this.state.w2wWif }
placeholder="Enter a WIF"
autoComplete="off"
required />
</div>
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10 padding-bottom-10">
<button
type="button"
className="btn btn-info col-sm-2"
onClick={ this.wif2wif }>
Get WIF
</button>
</div>
{ this.state.w2wResult &&
<div className="col-sm-12 form-group form-material no-padding-left margin-top-10">
<div>
<strong>WIF:</strong> { this.state.w2wResult.keys.priv }
</div>
<div className="margin-top-10">
<strong>Pub:</strong> { this.state.w2wResult.keys.pub }
</div>
</div>
}
</div>
);
}
}
export default ToolsWifToWif;

54
react/src/components/dashboard/walletsBalance/walletsBalance.js

@ -5,6 +5,10 @@ import {
getDashboardUpdate,
shepherdElectrumBalance,
} from '../../../actions/actionCreators';
import mainWindow from '../../../util/mainWindow';
import Config from '../../../config';
import { formatValue } from '../../../util/formatValue';
import ReactTooltip from 'react-tooltip';
import Store from '../../../store';
@ -65,7 +69,7 @@ class WalletsBalance extends React.Component {
}
}
renderBalance(type) {
renderBalance(type, returnFiatPrice) {
const _mode = this.props.ActiveCoin.mode;
let _balance = 0;
@ -102,7 +106,53 @@ class WalletsBalance extends React.Component {
}
}
return Number(_balance);
if (mainWindow.appConfig.fiatRates &&
this.props.Dashboard.prices &&
returnFiatPrice) {
const _prices = this.props.Dashboard.prices;
let _fiatPriceTotal = 0;
let _fiatPricePerCoin = 0;
if (this.props.ActiveCoin.coin === 'KMD') {
if (_prices.fiat &&
_prices.fiat.USD) {
_fiatPriceTotal = formatValue(_balance * _prices.fiat.USD);
_fiatPricePerCoin = _prices.fiat.USD;
}
} else {
if (_prices.fiat &&
_prices.fiat.USD &&
_prices[`${this.props.ActiveCoin.coin}/KMD`] &&
_prices[`${this.props.ActiveCoin.coin}/KMD`].low) {
_fiatPriceTotal = _balance * _prices.fiat.USD * _prices[`${this.props.ActiveCoin.coin}/KMD`].low;
_fiatPricePerCoin = _prices.fiat.USD * _prices[`${this.props.ActiveCoin.coin}/KMD`].low;
}
}
return (
<div>
<div>{ _balance }</div>
{ _fiatPriceTotal > 0 &&
_fiatPricePerCoin > 0 &&
<div
data-tip={ `Price per 1 ${this.props.ActiveCoin.coin} ~ $${formatValue(_fiatPricePerCoin)}` }
className="text-right">${ formatValue(_fiatPriceTotal) }</div>
}
{ _fiatPriceTotal > 0 &&
_fiatPricePerCoin > 0 &&
<ReactTooltip
effect="solid"
className="text-left" />
}
</div>
);
} else {
if (Config.roundValues) {
return formatValue(_balance);
} else {
return Number(_balance);
}
}
}
isActiveCoinMode(coinMode) {

24
react/src/components/dashboard/walletsBalance/walletsBalance.render.js

@ -1,9 +1,8 @@
import React from 'react';
import ReactTooltip from 'react-tooltip';
import { translate } from '../../../translate/translate';
import { formatValue } from '../../../util/formatValue';
import Config from '../../../config';
import Spinner from '../spinner/spinner';
import Config from '../../../config';
const WalletsBalanceRender = function() {
return (
@ -26,7 +25,7 @@ const WalletsBalanceRender = function() {
onClick={ this.refreshBalance }></i>
}
<div className="padding-20 padding-top-10">
<div className="clearfix">
<div className="clearfix cursor-default">
<div className="pull-left padding-vertical-10">
{ this.props.ActiveCoin.coin !== 'CHIPS' &&
this.props.ActiveCoin.mode !== 'spv' &&
@ -41,7 +40,7 @@ const WalletsBalanceRender = function() {
<span
className="pull-right padding-top-10 font-size-22"
data-tip={ Config.roundValues ? this.renderBalance('transparent') : '' }>
{ Config.roundValues ? formatValue(this.renderBalance('transparent')) : this.renderBalance('transparent') }
{ this.renderBalance('transparent', true) }
</span>
<ReactTooltip
effect="solid"
@ -55,16 +54,19 @@ const WalletsBalanceRender = function() {
<div className={ (this.props.ActiveCoin.mode === 'native' && Number(this.renderBalance('private'))) > 0 ? 'col-lg-3 col-xs-12' : 'hide' }>
<div className="widget widget-shadow">
<div className="padding-20 padding-top-10">
<div className="clearfix">
<div className="clearfix cursor-default">
<div className="pull-left padding-vertical-10">
<i className="icon fa-eye-slash font-size-24 vertical-align-bottom margin-right-5"></i>
{ translate('INDEX.Z_BALANCE') }
</div>
<span
className="pull-right padding-top-10 font-size-22"
title={ this.renderBalance('private') }>
{ Config.roundValues ? formatValue(this.renderBalance('private')) : this.renderBalance('private') }
data-tip={ Config.roundValues ? this.renderBalance('private') : '' }>
{ this.renderBalance('private', true) }
</span>
<ReactTooltip
effect="solid"
className="text-left" />
</div>
</div>
</div>
@ -74,7 +76,7 @@ const WalletsBalanceRender = function() {
<div className="widget widget-shadow">
<div className="widget-content">
<div className="padding-20 padding-top-10">
<div className="clearfix">
<div className="clearfix cursor-default">
<div className="pull-left padding-vertical-10">
<i className="icon fa-money font-size-24 vertical-align-bottom margin-right-5"></i>
{ translate('INDEX.INTEREST_EARNED') }
@ -82,7 +84,7 @@ const WalletsBalanceRender = function() {
<span
className="pull-right padding-top-10 font-size-22"
data-tip={ Config.roundValues ? this.renderBalance('interest') : '' }>
{ Config.roundValues ? formatValue(this.renderBalance('interest')) : this.renderBalance('interest') }
{ this.renderBalance('interest', true) }
</span>
<ReactTooltip
effect="solid"
@ -97,7 +99,7 @@ const WalletsBalanceRender = function() {
<div className="widget widget-shadow">
<div className="widget-content">
<div className="padding-20 padding-top-10">
<div className="clearfix">
<div className="clearfix cursor-default">
<div className="pull-left padding-vertical-10">
<i className="icon fa-bullseye font-size-24 vertical-align-bottom margin-right-5"></i>
{ translate('INDEX.TOTAL_BALANCE') }
@ -105,7 +107,7 @@ const WalletsBalanceRender = function() {
<span
className="pull-right padding-top-10 font-size-22"
data-tip={ Config.roundValues ? this.renderBalance('total') : '' }>
{ Config.roundValues ? formatValue(this.renderBalance('total')) : this.renderBalance('total') }
{ this.renderBalance('total', true) }
</span>
<ReactTooltip
effect="solid"

2
react/src/components/dashboard/walletsBalance/walletsBalance.scss

@ -15,7 +15,7 @@
.balance-placeholder--bold {
.pull-left {
font-weight: 500;
font-size: 16px;
font-size: 20px;
padding-top: 16px !important;
}
}

63
react/src/components/dashboard/walletsMain/walletsMain.js

@ -2,23 +2,84 @@ import React from 'react';
import { connect } from 'react-redux';
import WalletsMainRender from './walletsMain.render';
import { translate } from '../../../translate/translate';
import { triggerToaster } from '../../../actions/actionCreators';
import {
triggerToaster,
prices,
} from '../../../actions/actionCreators';
import { getCoinTitle } from '../../../util/coinHelper';
import Config from '../../../config';
import Store from '../../../store';
import mainWindow from '../../../util/mainWindow';
import { SocketProvider } from 'socket.io-react';
import io from 'socket.io-client';
const socket = io.connect(`http://127.0.0.1:${Config.agamaPort}`);
const PRICES_UPDATE_INTERVAL = 120000; // every 2m
class WalletsMain extends React.Component {
constructor() {
super();
this.getCoinStyle = this.getCoinStyle.bind(this);
this.pricesInterval = null;
socket.on('service', msg => this.updateSocketsData(msg));
}
componentWillUnmount() {
if (this.pricesInterval) {
clearInterval(this.pricesInterval);
}
}
componentWillMount() {
Store.dispatch(prices());
this.pricesInterval = setInterval(() => {
Store.dispatch(prices());
}, PRICES_UPDATE_INTERVAL);
if (mainWindow.createSeed.triggered &&
!mainWindow.createSeed.secondaryLoginPH) {
Store.dispatch(
triggerToaster(
'Please write down your public address, logout and login into Agama again to verify that your seed is correct.',
'First time seed use',
'info',
false
)
);
} else if (mainWindow.createSeed.triggered && mainWindow.createSeed.secondaryLoginPH) {
if (mainWindow.createSeed.secondaryLoginPH === mainWindow.createSeed.firstLoginPH) {
Store.dispatch(
triggerToaster(
'Your seed appears to be correct. As a final check up please double check that the public address you wrote down earlier is matching the one you see right now.',
'Congrats, you are all set!',
'success',
false
)
);
mainWindow.createSeed = {
triggered: false,
firstLoginPH: null,
secondaryLoginPH: null,
};
} else {
Store.dispatch(
triggerToaster(
'Your seed doesn\'t seem to be correct. Please logout and repeat wallet creation procedure again.',
'Seed verification error!',
'error',
false
)
);
mainWindow.createSeed = {
triggered: false,
firstLoginPH: null,
secondaryLoginPH: null,
};
}
}
}
updateSocketsData(data) {
if (data &&
data.komodod &&

5
react/src/components/login/login.js

@ -26,6 +26,7 @@ import {
loginWithPin,
} from '../../actions/actions/pin';
import mainWindow from '../../util/mainWindow';
import { md5 } from '../../util/crypto/md5';
const IGUNA_ACTIVE_HANDLE_TIMEOUT = 3000;
const IGUNA_ACTIVE_COINS_TIMEOUT = 10000;
@ -318,6 +319,7 @@ class Login extends React.Component {
}
loginSeed() {
mainWindow.createSeed.secondaryLoginPH = md5(this.state.loginPassphrase);
// reset the login pass phrase values so that when the user logs out, the values are clear
this.setState({
loginPassphrase: '',
@ -398,6 +400,9 @@ class Login extends React.Component {
}
execWalletCreate() {
mainWindow.createSeed.triggered = true;
mainWindow.createSeed.firstLoginPH = md5(this.state.randomSeed);
Store.dispatch(
shepherdElectrumAuth(this.state.randomSeedConfirm)
);

4
react/src/components/overrides.scss

@ -324,4 +324,8 @@ select{
&.type-dark {
background-color: #000;
}
}
.cursor-default {
cursor: default;
}

1
react/src/components/toaster/toaster-item.js

@ -21,7 +21,6 @@ class ToasterItem extends React.Component {
toastId: props.toastId,
className: props.className,
};
this.dismissToast = this.dismissToast.bind(this);
this.renderLB = this.renderLB.bind(this);
this.timeoutHandler = null;

7
react/src/reducers/dashboard.js

@ -8,7 +8,7 @@ import {
DASHBOARD_ELECTRUM_COINS,
ELECTRUM_SERVER_CHANGED,
DISPLAY_ZCASH_PARAMS_FETCH,
FIAT_RATES,
PRICES,
} from '../actions/storeType';
export function Dashboard(state = {
@ -21,6 +21,7 @@ export function Dashboard(state = {
electrumCoins: {},
eletrumServerChanged: false,
displayZcparamsModal: false,
prices: null,
}, action) {
switch (action.type) {
case DASHBOARD_ELECTRUM_COINS:
@ -72,10 +73,10 @@ export function Dashboard(state = {
...state,
eletrumServerChanged: action.eletrumServerChanged,
};
case FIAT_RATES:
case PRICES:
return {
...state,
fiatRates: action.fiatRates,
prices: action.prices,
};
default:
return state;

1
react/src/styles/index.scss

@ -44,6 +44,7 @@
@import '../components/dashboard/navbar/navbar.scss';
@import '../components/dashboard/settings/settings.scss';
@import '../components/dashboard/walletsData/walletsData.scss';
@import '../components/dashboard/walletsBalance/walletsBalance.scss';
@import '../components/dashboard/claimInterestModal/claimInterestModal.scss';
@import '../components/dashboard/importKeyModal/importKeyModal.scss';
@import '../components/dashboard/jumblr/jumblr.scss';

9
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;
Loading…
Cancel
Save