473 lines
20 KiB
HTML
473 lines
20 KiB
HTML
<html>
|
|
<head>
|
|
<title>Ethereum Wallet</title>
|
|
<link rel="stylesheet" type="text/css" href="./style.css">
|
|
</head>
|
|
<body>
|
|
<div class="centerer" id="screen-select">
|
|
<div class="centered">
|
|
<h1>Ethereum Wallet Tool</h1>
|
|
<hr />
|
|
<h2>Load JSON Wallet</h2>
|
|
<p>
|
|
If you have a JSON wallet file from <i>geth</i> or from the initial <i>Ethereum</i>
|
|
crowd sale, you can decrypt it here. No information is shared with <b>any</b>
|
|
server.
|
|
</p>
|
|
<table>
|
|
<tr>
|
|
<th>JSON Wallet:</th>
|
|
<td><div class="file" id="select-wallet-drop">Drop JSON wallet file here</div><input type="file" id="select-wallet-file" /></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Password:</th>
|
|
<td><input type="password" placeholder="(password)" id="select-wallet-password" /></td>
|
|
</tr>
|
|
<tr>
|
|
<td> </td>
|
|
<td>
|
|
<div id="select-submit-wallet" class="submit disable">Unlock JSON Wallet</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<hr />
|
|
<h2>Mnemonic Phrase Wallet</h2>
|
|
<p>
|
|
If you have a 12 word mnemonic phrase, you can generate your wallet here.
|
|
No information is shared with <b>any</b> server.
|
|
</p>
|
|
<table>
|
|
<tr>
|
|
<th>Mnemonic Phrase:</th>
|
|
<td><input type="text" placeholder="(mnemonic phrase)" id="select-mnemonic-phrase" /></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Path:</th>
|
|
<td><input type="text" placeholder="(path)" id="select-mnemonic-path" value="m/44'/60'/0'/0/0" /></td>
|
|
</tr>
|
|
<tr>
|
|
<td> </td>
|
|
<td>
|
|
<div id="select-submit-mnemonic" class="submit disable">Derive Wallet</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<hr />
|
|
<h2>Raw Private Key</h2>
|
|
<p>
|
|
</p>
|
|
<table>
|
|
<tr>
|
|
<th>Private Key:</th>
|
|
<td><input type="text" placeholder="(private key)" id="select-privatekey" /></td>
|
|
</tr>
|
|
<tr>
|
|
<td> </td>
|
|
<td>
|
|
<div id="select-submit-privatekey" class="submit disable">Load Raw Private Key</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<hr />
|
|
<h3>Disclaimer:</h3>
|
|
<p>
|
|
This is beta software, with no warranty. <b>Use at your own risk.</b>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="centerer hidden" id="screen-loading">
|
|
<div class="centered">
|
|
<h1>Loading Wallet</h1>
|
|
<hr />
|
|
<h2 id="loading-header"></h2>
|
|
<table>
|
|
<tr>
|
|
<th>Progress:</th>
|
|
<td>
|
|
<input type="text" readonly="readonly" class="readonly" id="loading-status" value="" /></div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td> </td>
|
|
<td>
|
|
<div id="loading-cancel" class="submit">Cancel</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<hr />
|
|
<h3>Disclaimer:</h3>
|
|
<p>
|
|
This is beta software, with no warranty. <b>Use at your own risk.</b>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="centerer hidden" id="screen-wallet">
|
|
<div class="centered">
|
|
<h1>Ethereum Wallet<span id="wallet-username" class="username right"></span></h1>
|
|
<hr />
|
|
<h3>Wallet Details:</h3>
|
|
<table>
|
|
<tr>
|
|
<th>Address:</th>
|
|
<td>
|
|
<input type="text" readonly="readonly" class="readonly" id="wallet-address" value="" /></div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Network:</th>
|
|
<td>
|
|
<div class="clearfix"></div>
|
|
<div class="network option" data-network="ropsten">Ropsten<br /><span>testnet</span></div>
|
|
<div class="network option" data-network="rinkeby">Rinkeby<br /><span>testnet</span></div>
|
|
<div class="network option" data-network="kovan">Kovan<br /><span>testnet</span></div>
|
|
<div class="network option last selected" data-network="homestead">Homestead<br /><span>mainnet</span></div>
|
|
<div class="clearfix"></div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Balance:</th>
|
|
<td>
|
|
<input type="text" readonly="readonly" class="readonly" id="wallet-balance" value="0.0" /></div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Nonce:</th>
|
|
<td>
|
|
<input type="text" readonly="readonly" class="readonly" id="wallet-transaction-count" value="0" /></div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td> </td>
|
|
<td>
|
|
<div id="wallet-submit-refresh" class="submit">Refresh</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<h3>Send Ether:</h3>
|
|
<table>
|
|
<tr>
|
|
<th>Target Address:</th>
|
|
<td><input type="text" placeholder="(target address)" id="wallet-send-target-address" /></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Amount:</th>
|
|
<td><input type="text" placeholder="(amount)" id="wallet-send-amount" /></td>
|
|
</tr>
|
|
<tr>
|
|
<td> </td>
|
|
<td>
|
|
<div id="wallet-submit-send" class="submit disable">Send Ether</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<h3>Session Activity</h3>
|
|
<div id="wallet-activity" class="activity"></div>
|
|
|
|
<hr />
|
|
<h3>Disclaimer:</h3>
|
|
<p>
|
|
This is beta software, with no warranty. <b>Use at your own risk.</b>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript" src="../../dist/ethers.js"></script>
|
|
<script type="text/javascript">
|
|
function query(el, selector) {
|
|
if (!selector) {
|
|
selector = el;
|
|
el = document;
|
|
}
|
|
return Array.prototype.slice.call(el.querySelectorAll(selector));
|
|
}
|
|
|
|
function setEnter(source, target) {
|
|
source.onkeyup = function(e) {
|
|
if (e.which === 13) { target.click(); }
|
|
}
|
|
}
|
|
|
|
var cancelScrypt = false;
|
|
document.getElementById('loading-cancel').onclick = function() {
|
|
cancelScrypt = true;
|
|
};
|
|
|
|
var updateLoading = (function() {
|
|
var loadingStatus = document.getElementById('loading-status');
|
|
return (function(progress) {
|
|
loadingStatus.value = (parseInt(progress * 100)) + '%';
|
|
return cancelScrypt;
|
|
});
|
|
})();
|
|
|
|
// JSON Wallet
|
|
(function() {
|
|
var inputFile = document.getElementById('select-wallet-file');
|
|
var targetDrop = document.getElementById('select-wallet-drop');
|
|
var inputPassword = document.getElementById('select-wallet-password');
|
|
var submit = document.getElementById('select-submit-wallet');
|
|
|
|
function check() {
|
|
if (inputFile.files && inputFile.files.length === 1) {
|
|
submit.classList.remove('disable');
|
|
targetDrop.textContent = inputFile.files[0].name;
|
|
} else {
|
|
submit.classList.add('disable');
|
|
}
|
|
}
|
|
inputFile.onchange = check;
|
|
inputPassword.oninput = check;
|
|
|
|
setEnter(inputPassword, submit);
|
|
|
|
inputFile.addEventListener('dragover', function(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
targetDrop.classList.add('highlight');
|
|
}, true);
|
|
|
|
inputFile.addEventListener('drop', function(event) {
|
|
targetDrop.classList.remove('highlight');
|
|
}, true);
|
|
|
|
submit.onclick = function() {
|
|
if (submit.classList.contains('disable')) { return; }
|
|
|
|
var fileReader = new FileReader();
|
|
fileReader.onload = function(e) {
|
|
var json = e.target.result;
|
|
|
|
if (ethers.Wallet.isEncryptedWallet(json)) {
|
|
showLoading('Decrypting Wallet...');
|
|
|
|
cancelScrypt = false;
|
|
|
|
ethers.Wallet.fromEncryptedJson(json, inputPassword.value, updateLoading).then(function(wallet) {
|
|
showWallet(wallet);
|
|
|
|
}, function(error) {
|
|
if (error.message === 'invalid password') {
|
|
alert('Wrong Password');
|
|
} else {
|
|
console.log(error);
|
|
alert('Error Decrypting Wallet');
|
|
}
|
|
showSelect();
|
|
});
|
|
} else {
|
|
alert('Unknown JSON wallet format');
|
|
}
|
|
};
|
|
fileReader.readAsText(inputFile.files[0]);
|
|
};
|
|
|
|
})();
|
|
|
|
// Raw Private Key
|
|
(function() {
|
|
var inputPrivatekey = document.getElementById('select-privatekey');
|
|
var submit = document.getElementById('select-submit-privatekey');
|
|
|
|
function check() {
|
|
if (inputPrivatekey.value.match(/^(0x)?[0-9A-fa-f]{64}$/)) {
|
|
submit.classList.remove('disable');
|
|
} else {
|
|
submit.classList.add('disable');
|
|
}
|
|
}
|
|
inputPrivatekey.oninput = check;
|
|
|
|
setEnter(inputPrivatekey, submit);
|
|
|
|
submit.onclick = function() {
|
|
if (submit.classList.contains('disable')) { return; }
|
|
var privateKey = inputPrivatekey.value;
|
|
if (privateKey.substring(0, 2) !== '0x') { privateKey = '0x' + privateKey; }
|
|
showWallet(new ethers.Wallet(privateKey));
|
|
}
|
|
})();
|
|
|
|
// Mnemonic Phrase
|
|
(function() {
|
|
var inputPhrase = document.getElementById('select-mnemonic-phrase');
|
|
var inputPath = document.getElementById('select-mnemonic-path');
|
|
var submit = document.getElementById('select-submit-mnemonic');
|
|
|
|
function check() {
|
|
if (ethers.HDNode.isValidMnemonic(inputPhrase.value)) {
|
|
submit.classList.remove('disable');
|
|
} else {
|
|
submit.classList.add('disable');
|
|
}
|
|
}
|
|
inputPhrase.oninput = check;
|
|
inputPath.oninput = check;
|
|
|
|
setEnter(inputPhrase, submit);
|
|
setEnter(inputPath, submit);
|
|
|
|
submit.onclick = function() {
|
|
if (submit.classList.contains('disable')) { return; }
|
|
showWallet(ethers.Wallet.fromMnemonic(inputPhrase.value, inputPath.value));
|
|
}
|
|
})();
|
|
|
|
|
|
var activeWallet = null;
|
|
|
|
function showError(error) {
|
|
alert('Error \u2014 ' + error.message);
|
|
}
|
|
|
|
// Refresh balance and transaction count in the UI
|
|
var refresh = (function() {
|
|
var inputBalance = document.getElementById('wallet-balance');
|
|
var inputTransactionCount = document.getElementById('wallet-transaction-count');
|
|
var submit = document.getElementById('wallet-submit-refresh');
|
|
|
|
function refresh() {
|
|
addActivity('> Refreshing details...');
|
|
activeWallet.getBalance('pending').then(function(balance) {
|
|
addActivity('< Balance: ' + balance.toString(10));
|
|
inputBalance.value = ethers.utils.formatEther(balance, { commify: true });
|
|
}, function(error) {
|
|
showError(error);
|
|
});
|
|
activeWallet.getTransactionCount('pending').then(function(transactionCount) {
|
|
addActivity('< TransactionCount: ' + transactionCount);
|
|
inputTransactionCount.value = transactionCount;
|
|
}, function(error) {
|
|
showError(error);
|
|
});
|
|
}
|
|
submit.onclick = refresh;
|
|
|
|
return refresh;
|
|
})();
|
|
|
|
var addActivity = (function() {
|
|
var activity = document.getElementById('wallet-activity');
|
|
return function(message, url) {
|
|
var line = document.createElement('a');
|
|
line.textContent = message;
|
|
if (url) {
|
|
line.setAttribute('href', url);
|
|
line.setAttribute('target', '_blank');
|
|
}
|
|
activity.appendChild(line);
|
|
}
|
|
})();
|
|
|
|
// Set up the wallet page
|
|
(function() {
|
|
|
|
var inputTargetAddress = document.getElementById('wallet-send-target-address');
|
|
var inputAmount = document.getElementById('wallet-send-amount');
|
|
var submit = document.getElementById('wallet-submit-send');
|
|
|
|
// Validate the address and value (to enable the send button)
|
|
function check() {
|
|
try {
|
|
ethers.utils.getAddress(inputTargetAddress.value);
|
|
ethers.utils.parseEther(inputAmount.value);
|
|
} catch (error) {
|
|
submit.classList.add('disable');
|
|
return;
|
|
}
|
|
submit.classList.remove('disable');
|
|
}
|
|
inputTargetAddress.oninput = check;
|
|
inputAmount.oninput = check;
|
|
|
|
query('.network.option').forEach(function(el) {
|
|
var network = el.getAttribute('data-network');
|
|
el.onclick = function() {
|
|
addActivity('! Switched network: ' + network);
|
|
activeWallet = activeWallet.connect(ethers.providers.getDefaultProvider(network));
|
|
query('.network.option.selected').forEach(function(el) {
|
|
el.classList.remove('selected');
|
|
});
|
|
el.classList.add('selected');
|
|
refresh();
|
|
};
|
|
});
|
|
|
|
// Send ether
|
|
submit.onclick = function() {
|
|
|
|
// Matt (from Etherscan) is working on a gasPrice API call, which
|
|
// should be done within a week or so.
|
|
// @TODO
|
|
//var gasPrice = (activeWallet.provider.testnet ? 0x4a817c800: 0xba43b7400);
|
|
//console.log('GasPrice: ' + gasPrice);
|
|
|
|
var targetAddress = ethers.utils.getAddress(inputTargetAddress.value);
|
|
var amountWei = ethers.utils.parseEther(inputAmount.value);
|
|
activeWallet.send(targetAddress, amountWei, {
|
|
gasPrice: activeWallet.provider.getGasPrice(),
|
|
gasLimit: 21000,
|
|
}).then(function(tx) {
|
|
console.log(tx);
|
|
|
|
// Since we only use standard networks, network will always be known
|
|
var tag = activeWallet.provider.network.name + '.';
|
|
if (tag === 'homestead.') { tag = ''; }
|
|
var url = 'https://' + tag + 'etherscan.io/tx/' + tx.hash;
|
|
addActivity('< Transaction sent: ' + tx.hash.substring(0, 20) + '...', url);
|
|
alert('Success!');
|
|
|
|
inputTargetAddress.value = '';
|
|
inputAmount.value = '';
|
|
submit.classList.add('disable');
|
|
|
|
refresh();
|
|
}, function(error) {
|
|
console.log(error);
|
|
showError(error);
|
|
});
|
|
}
|
|
})();
|
|
|
|
function showSelect() {
|
|
document.getElementById('screen-select').style.display = 'block';
|
|
document.getElementById('screen-loading').style.display = 'none';
|
|
document.getElementById('screen-wallet').style.display = 'none';
|
|
}
|
|
|
|
function showLoading(title) {
|
|
document.getElementById('screen-select').style.display = 'none';
|
|
document.getElementById('screen-loading').style.display = 'block';
|
|
document.getElementById('screen-wallet').style.display = 'none';
|
|
|
|
document.getElementById('loading-header').textContent = title;
|
|
}
|
|
|
|
function showWallet(wallet) {
|
|
var network = document.querySelector('.network.option.selected').getAttribute('data-network');
|
|
activeWallet = wallet.connect(new ethers.providers.getDefaultProvider(network));
|
|
|
|
document.getElementById('screen-select').style.display = 'none';
|
|
document.getElementById('screen-loading').style.display = 'none';
|
|
document.getElementById('screen-wallet').style.display = 'block';
|
|
|
|
var inputWalletAddress = document.getElementById('wallet-address');
|
|
inputWalletAddress.value = wallet.address;
|
|
inputWalletAddress.onclick = function() {
|
|
this.select();
|
|
};
|
|
|
|
refresh();
|
|
}
|
|
|
|
//var privateKey = '0x3141592653589793238462643383279502884197169399375105820974944592';
|
|
//showWallet(new ethers.Wallet(privateKey));
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|