pbca26
7 years ago
28 changed files with 629 additions and 68 deletions
@ -0,0 +1,243 @@ |
|||
import React from 'react'; |
|||
import { connect } from 'react-redux'; |
|||
import ReactDOM from 'react-dom'; |
|||
import Store from '../../../store'; |
|||
import { |
|||
displayImportKeyModal, |
|||
importPrivkey, |
|||
triggerToaster, |
|||
copyCoinAddress, |
|||
getDebugLog, |
|||
getDashboardUpdateState |
|||
} from '../../../actions/actionCreators'; |
|||
import { translate } from '../../../translate/translate'; |
|||
import { |
|||
ImportKeyModalRender, |
|||
} from './importKeyModal.render'; |
|||
|
|||
// import gen komodo keys utils
|
|||
import '../../../util/crypto/gen/array.map.js'; |
|||
import '../../../util/crypto/gen/cryptojs.js'; |
|||
import '../../../util/crypto/gen/cryptojs.sha256.js'; |
|||
import '../../../util/crypto/gen/cryptojs.pbkdf2.js'; |
|||
import '../../../util/crypto/gen/cryptojs.hmac.js'; |
|||
import '../../../util/crypto/gen/cryptojs.aes.js'; |
|||
import '../../../util/crypto/gen/cryptojs.blockmodes.js'; |
|||
import '../../../util/crypto/gen/cryptojs.ripemd160.js'; |
|||
import '../../../util/crypto/gen/securerandom.js'; |
|||
import '../../../util/crypto/gen/ellipticcurve.js'; |
|||
import '../../../util/crypto/gen/biginteger.js'; |
|||
import '../../../util/crypto/gen/crypto-scrypt.js'; |
|||
import { Bitcoin } from '../../../util/crypto/gen/bitcoin.js'; |
|||
|
|||
class ImportKeyModal extends React.Component { |
|||
constructor() { |
|||
super(); |
|||
this.state = { |
|||
open: false, |
|||
wif: '', |
|||
wifkeysPassphrase: '', |
|||
passphraseWif: null, |
|||
passphraseAddress: null, |
|||
keyImportResult: null, |
|||
importWithRescan: false, |
|||
seedInputVisibility: false, |
|||
toggleSeedInputVisibility: false, |
|||
trimPassphraseTimer: null, |
|||
}; |
|||
this.generateKeysFromPassphrase = this.generateKeysFromPassphrase.bind(this); |
|||
this.toggleImportWithRescan = this.toggleImportWithRescan.bind(this); |
|||
this.toggleSeedInputVisibility = this.toggleSeedInputVisibility.bind(this); |
|||
this._copyCoinAddress = this._copyCoinAddress.bind(this); |
|||
this.showPassphraseAddress = this.showPassphraseAddress.bind(this); |
|||
this.importWifAddress = this.importWifAddress.bind(this); |
|||
this.updateInput = this.updateInput.bind(this); |
|||
this.importFromPassphrase = this.importFromPassphrase.bind(this); |
|||
this.importFromWif = this.importFromWif.bind(this); |
|||
} |
|||
|
|||
_copyCoinAddress(address) { |
|||
Store.dispatch(copyCoinAddress(address)); |
|||
} |
|||
|
|||
updateInput(e) { |
|||
if (e.target.name === 'wifkeysPassphrase') { |
|||
// remove any empty chars from the start/end of the string
|
|||
const newValue = e.target.value; |
|||
|
|||
clearTimeout(this.state.trimPassphraseTimer); |
|||
|
|||
const _trimPassphraseTimer = setTimeout(() => { |
|||
this.setState({ |
|||
wifkeysPassphrase: newValue ? newValue.trim() : '', // hardcoded field name
|
|||
}); |
|||
}, 2000); |
|||
|
|||
this.resizeLoginTextarea(); |
|||
|
|||
this.setState({ |
|||
trimPassphraseTimer: _trimPassphraseTimer, |
|||
[e.target.name]: newValue, |
|||
}); |
|||
} else { |
|||
this.setState({ |
|||
[e.target.name]: e.target.value, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
resizeLoginTextarea() { |
|||
// auto-size textarea
|
|||
setTimeout(() => { |
|||
if (this.state.seedInputVisibility) { |
|||
document.querySelector('#wifkeysPassphraseTextarea').style.height = '1px'; |
|||
document.querySelector('#wifkeysPassphraseTextarea').style.height = `${(15 + document.querySelector('#wifkeysPassphraseTextarea').scrollHeight)}px`; |
|||
} |
|||
}, 100); |
|||
} |
|||
|
|||
toggleSeedInputVisibility() { |
|||
this.setState({ |
|||
seedInputVisibility: !this.state.seedInputVisibility, |
|||
}); |
|||
} |
|||
|
|||
showPassphraseAddress() { |
|||
const _address = this.generateKeysFromPassphrase(); |
|||
|
|||
if (_address) { |
|||
this.setState({ |
|||
passphraseAddress: _address.address, |
|||
passphraseWif: _address.wif, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
importFromPassphrase() { |
|||
const _address = this.generateKeysFromPassphrase(); |
|||
|
|||
if (_address) { |
|||
this.importWifAddress(_address.wif, true); |
|||
} |
|||
} |
|||
|
|||
importFromWif() { |
|||
this.importWifAddress(this.state.wif, this.state.importWithRescan); |
|||
} |
|||
|
|||
importWifAddress(wif, rescan) { |
|||
let _rescanInProgress = true; |
|||
|
|||
if (rescan) { |
|||
setTimeout(() => { |
|||
if (_rescanInProgress) { |
|||
setTimeout(() => { |
|||
if (this.props.ActiveCoin.coin === 'KMD') { |
|||
Store.dispatch(getDebugLog('komodo', 100)); |
|||
} else { |
|||
Store.dispatch(getDebugLog('komodo', 100, this.props.ActiveCoin.coin)); |
|||
} |
|||
}, 2000); |
|||
|
|||
Store.dispatch(getDashboardUpdateState(null, this.props.ActiveCoin.coin, true)); |
|||
Store.dispatch( |
|||
triggerToaster( |
|||
'Address imported. Wallet rescan is in progress. Please wait until it\s finished.', |
|||
translate('TOASTR.WALLET_NOTIFICATION'), |
|||
'info', |
|||
false |
|||
) |
|||
); |
|||
this.closeModal(); |
|||
} |
|||
}, 2000); |
|||
} |
|||
|
|||
importPrivkey( |
|||
this.props.ActiveCoin.coin, |
|||
wif, |
|||
rescan |
|||
).then((json) => { |
|||
_rescanInProgress = false; |
|||
|
|||
if (!json.id && |
|||
!json.result && |
|||
!json.error) { |
|||
// console.warn('importPrivkey', json);
|
|||
Store.dispatch( |
|||
triggerToaster( |
|||
rescan ? 'Wallet rescan finished' : 'Address imported', |
|||
translate('TOASTR.WALLET_NOTIFICATION'), |
|||
'success', |
|||
false |
|||
) |
|||
); |
|||
} else { |
|||
Store.dispatch( |
|||
triggerToaster( |
|||
json.error.message, |
|||
'Error', |
|||
'error' |
|||
) |
|||
); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
generateKeysFromPassphrase() { |
|||
if (this.state.wifkeysPassphrase && |
|||
this.state.wifkeysPassphrase.length) { |
|||
const bytes = Crypto.SHA256(this.state.wifkeysPassphrase, { asBytes: true }); |
|||
// byte flipping to make it compat with iguana core
|
|||
bytes[0] &= 248; |
|||
bytes[31] &= 127; |
|||
bytes[31] |= 64; |
|||
const btcKey = new Bitcoin.ECKey(bytes).setCompressed(true); |
|||
const kmdAddress = btcKey.getBitcoinAddress(); |
|||
const wifAddress = btcKey.getBitcoinWalletImportFormat(); |
|||
|
|||
return { |
|||
address: kmdAddress, |
|||
wif: wifAddress, |
|||
}; |
|||
} else { |
|||
Store.dispatch( |
|||
triggerToaster( |
|||
'Empty passphrase field', |
|||
'Error', |
|||
'error' |
|||
) |
|||
); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
toggleImportWithRescan() { |
|||
this.setState({ |
|||
importWithRescan: !this.state.importWithRescan, |
|||
}); |
|||
} |
|||
|
|||
closeModal() { |
|||
Store.dispatch(displayImportKeyModal(false)); |
|||
} |
|||
|
|||
render() { |
|||
return ImportKeyModalRender.call(this); |
|||
} |
|||
} |
|||
|
|||
const mapStateToProps = (state) => { |
|||
return { |
|||
ActiveCoin: { |
|||
coin: state.ActiveCoin.coin, |
|||
balance: state.ActiveCoin.balance, |
|||
}, |
|||
Dashboard: { |
|||
displayImportKeyModal: state.Dashboard.displayImportKeyModal, |
|||
}, |
|||
}; |
|||
}; |
|||
|
|||
export default connect(mapStateToProps)(ImportKeyModal); |
@ -0,0 +1,136 @@ |
|||
import React from 'react'; |
|||
import { translate } from '../../../translate/translate'; |
|||
|
|||
/*export const _ClaimInterestTableRender = function() { |
|||
};*/ |
|||
|
|||
export const ImportKeyModalRender = function() { |
|||
return ( |
|||
<span> |
|||
<div className={ 'modal modal-import-key modal-3d-sign ' + (this.props.Dashboard.displayImportKeyModal ? 'show in' : 'fade hide') }> |
|||
<div className="modal-dialog modal-center modal-sm"> |
|||
<div className="modal-content"> |
|||
<div className="modal-header bg-orange-a400 wallet-send-header"> |
|||
<button |
|||
type="button" |
|||
className="close white" |
|||
onClick={ this.closeModal }> |
|||
<span>×</span> |
|||
</button> |
|||
<h4 className="modal-title white text-left">Import key</h4> |
|||
</div> |
|||
<div className="modal-body"> |
|||
<div className="padding-bottom-40"> |
|||
Two forms below allow you to import either <strong>Iguana Core / ICO</strong> passphrase (seed) or <strong>WIF (Wallet Import Format)</strong> key. |
|||
</div> |
|||
<div> |
|||
<strong>Passphrase / seed</strong> |
|||
<p className="margin-top-10"> |
|||
<strong>Notice:</strong> importing a passphrase will trigger a full wallet rescan. |
|||
<span className={ this.props.ActiveCoin.coin === 'KMD' ? '' : 'hide' }>This process can take hours to rescan the whole blockchain.</span> |
|||
</p> |
|||
<form |
|||
className="wifkeys-form" |
|||
method="post" |
|||
action="javascript:" |
|||
autoComplete="off"> |
|||
<div className="form-group form-material floating"> |
|||
<input |
|||
type="password" |
|||
className={ !this.state.seedInputVisibility ? 'form-control' : 'hide' } |
|||
name="wifkeysPassphrase" |
|||
id="wifkeysPassphrase" |
|||
onChange={ this.updateInput } |
|||
value={ this.state.wifkeysPassphrase } /> |
|||
<textarea |
|||
className={ this.state.seedInputVisibility ? 'form-control' : 'hide' } |
|||
id="wifkeysPassphraseTextarea" |
|||
name="wifkeysPassphrase" |
|||
onChange={ this.updateInput } |
|||
value={ this.state.wifkeysPassphrase }></textarea> |
|||
<i |
|||
className={ 'seed-toggle fa fa-eye' + (!this.state.seedInputVisibility ? '-slash' : '') } |
|||
onClick={ this.toggleSeedInputVisibility }></i> |
|||
<label |
|||
className="floating-label" |
|||
htmlFor="wifkeysPassphrase">{ translate('INDEX.PASSPHRASE') }</label> |
|||
</div> |
|||
<div className="text-align-center"> |
|||
<button |
|||
type="button" |
|||
className="btn btn-primary waves-effect waves-light margin-right-20" |
|||
onClick={ this.importFromPassphrase }>Import</button> |
|||
<button |
|||
type="button" |
|||
className="btn btn-primary waves-effect waves-light" |
|||
onClick={ this.showPassphraseAddress }>Show address and WIF</button> |
|||
</div> |
|||
</form> |
|||
{ this.state.passphraseAddress && this.state.passphraseWif && |
|||
<div className="margin-top-60"> |
|||
<p> |
|||
<strong>Address: </strong> { this.state.passphraseAddress } |
|||
<button |
|||
className="btn btn-default btn-xs clipboard-edexaddr copy-string-btn" |
|||
title={ translate('INDEX.COPY_TO_CLIPBOARD') } |
|||
onClick={ () => this._copyCoinAddress(this.state.passphraseAddress) }> |
|||
<i className="icon wb-copy"></i> { translate('INDEX.COPY') } |
|||
</button> |
|||
</p> |
|||
<p> |
|||
<strong>WIF: </strong> { this.state.passphraseWif } |
|||
<button |
|||
className="btn btn-default btn-xs clipboard-edexaddr copy-string-btn" |
|||
title={ translate('INDEX.COPY_TO_CLIPBOARD') } |
|||
onClick={ () => this._copyCoinAddress(this.state.passphraseWif) }> |
|||
<i className="icon wb-copy"></i> { translate('INDEX.COPY') } |
|||
</button> |
|||
</p> |
|||
</div> |
|||
} |
|||
</div> |
|||
<div className="line">or</div> |
|||
<div> |
|||
<strong>WIF (Wallet Import Format)</strong> |
|||
<div className="toggle-box padding-top-20"> |
|||
<span className="pointer"> |
|||
<label className="switch"> |
|||
<input |
|||
type="checkbox" |
|||
checked={ this.state.importWithRescan } /> |
|||
<div |
|||
className="slider" |
|||
onClick={ this.toggleImportWithRescan }></div> |
|||
</label> |
|||
<div |
|||
className="toggle-label" |
|||
onClick={ this.toggleImportWithRescan }> |
|||
Trigger rescan |
|||
<i |
|||
className="icon fa-question-circle settings-help" |
|||
title="Use this option if you want to trigger rescan after WIF is imported. If you have several addresses that you want to import add them one by one and toggle this option on the last address import."></i> |
|||
</div> |
|||
</span> |
|||
</div> |
|||
<div className="margin-top-20"> |
|||
<label htmlFor="wif" className="bold">Wif key</label> |
|||
<input |
|||
type="text" |
|||
className="form-control" |
|||
name="wif" |
|||
onChange={ this.updateInput } |
|||
value={ this.state.wif } /> |
|||
</div> |
|||
<button |
|||
type="button" |
|||
className="btn btn-primary waves-effect waves-light margin-top-10" |
|||
onClick={ this.importFromWif }>{ this.state.importWithRescan ? 'Import and rescan' : 'Import' }</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div className={ 'modal-backdrop ' + (this.props.Dashboard.displayImportKeyModal ? 'show in' : 'fade hide') }></div> |
|||
</span> |
|||
); |
|||
}; |
@ -0,0 +1,33 @@ |
|||
.modal-import-key { |
|||
.modal-dialog { |
|||
width: 70%; |
|||
max-height: 90vh; |
|||
overflow-y: auto; |
|||
overflow-x: hidden; |
|||
} |
|||
.line { |
|||
padding: 50px 0 40px 0; |
|||
text-transform: uppercase; |
|||
font-weight: bold; |
|||
} |
|||
.line:before { |
|||
content: ''; |
|||
display: inline-block; |
|||
height: 1px; |
|||
width: 47%; |
|||
background: #ccc; |
|||
margin-right: 10px; |
|||
position: relative; |
|||
top: -4px; |
|||
} |
|||
.line:after { |
|||
content: ''; |
|||
display: inline-block; |
|||
height: 1px; |
|||
width: 47%; |
|||
background: #ccc; |
|||
margin-left: 10px; |
|||
position: relative; |
|||
top: -4px; |
|||
} |
|||
} |
Loading…
Reference in new issue